1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * GData Client
4  * Copyright (C) Philip Withnall 2009, 2010, 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-event
22  * @short_description: GData Calendar event object
23  * @stability: Stable
24  * @include: gdata/services/calendar/gdata-calendar-event.h
25  *
26  * #GDataCalendarEvent is a subclass of #GDataEntry to represent an event on a calendar from Google Calendar.
27  *
28  * For more details of Google Calendar's GData API, see the
29  * <ulink type="http" url="https://developers.google.com/google-apps/calendar/v3/reference/">
30  * online documentation</ulink>.
31  *
32  * <example>
33  * 	<title>Adding a New Event to the Default Calendar</title>
34  * 	<programlisting>
35  *	GDataCalendarService *service;
36  *	GDataCalendarEvent *event, *new_event;
37  *	GDataGDWhere *where;
38  *	GDataGDWho *who;
39  *	GDataGDWhen *when;
40  *	GTimeVal current_time;
41  *	GError *error = NULL;
42  *
43  *	/<!-- -->* Create a service *<!-- -->/
44  *	service = create_calendar_service ();
45  *
46  *	/<!-- -->* Create the new event *<!-- -->/
47  *	event = gdata_calendar_event_new (NULL);
48  *
49  *	gdata_entry_set_title (GDATA_ENTRY (event), "Event Title");
50  *	gdata_entry_set_content (GDATA_ENTRY (event), "Event description. This should be a few sentences long.");
51  *	gdata_calendar_event_set_status (event, GDATA_GD_EVENT_STATUS_CONFIRMED);
52  *
53  *	where = gdata_gd_where_new (NULL, "Description of the location", NULL);
54  *	gdata_calendar_event_add_place (event, where);
55  *	g_object_unref (where);
56  *
57  *	who = gdata_gd_who_new (GDATA_GD_WHO_EVENT_ORGANIZER, "John Smith", "john.smith@gmail.com");
58  *	gdata_calendar_event_add_person (event, who);
59  *	g_object_unref (who);
60  *
61  *	g_get_current_time (&current_time);
62  *	when = gdata_gd_when_new (current_time.tv_sec, current_time.tv_sec + 3600, FALSE);
63  *	gdata_calendar_event_add_time (event, when);
64  *	g_object_unref (when);
65  *
66  *	/<!-- -->* Insert the event in the calendar *<!-- -->/
67  *	new_event = gdata_calendar_service_insert_event (service, event, NULL, &error);
68  *
69  *	g_object_unref (event);
70  *	g_object_unref (service);
71  *
72  *	if (error != NULL) {
73  *		g_error ("Error inserting event: %s", error->message);
74  *		g_error_free (error);
75  *		return NULL;
76  *	}
77  *
78  *	/<!-- -->* Do something with the new_event here, such as return it to the user or store its ID for later usage *<!-- -->/
79  *
80  *	g_object_unref (new_event);
81  * 	</programlisting>
82  * </example>
83  */
84 
85 #include <config.h>
86 #include <glib.h>
87 #include <glib/gi18n-lib.h>
88 #include <string.h>
89 
90 #include "gdata-calendar-event.h"
91 #include "gdata-private.h"
92 #include "gdata-service.h"
93 #include "gdata-parser.h"
94 #include "gdata-types.h"
95 #include "gdata-comparable.h"
96 
97 static GObject *gdata_calendar_event_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params);
98 static void gdata_calendar_event_dispose (GObject *object);
99 static void gdata_calendar_event_finalize (GObject *object);
100 static void gdata_calendar_event_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
101 static void gdata_calendar_event_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
102 static void get_json (GDataParsable *parsable, JsonBuilder *builder);
103 static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error);
104 static gboolean post_parse_json (GDataParsable *parsable, gpointer user_data, GError **error);
105 static const gchar *get_content_type (void);
106 
107 struct _GDataCalendarEventPrivate {
108 	gint64 edited;
109 	gchar *status;
110 	gchar *visibility;
111 	gchar *transparency;
112 	gchar *uid;
113 	gint64 sequence;
114 	GList *times; /* GDataGDWhen */
115 	gboolean guests_can_modify;
116 	gboolean guests_can_invite_others;
117 	gboolean guests_can_see_guests;
118 	gboolean anyone_can_add_self;
119 	GList *people; /* GDataGDWho */
120 	GList *places; /* GDataGDWhere */
121 	gchar *recurrence;
122 	gchar *original_event_id;
123 	gchar *original_event_uri;
124 	gchar *organiser_email;  /* owned */
125 
126 	/* Parsing state. */
127 	struct {
128 		gint64 start_time;
129 		gint64 end_time;
130 		gboolean seen_start;
131 		gboolean seen_end;
132 		gboolean start_is_date;
133 		gboolean end_is_date;
134 	} parser;
135 };
136 
137 enum {
138 	PROP_EDITED = 1,
139 	PROP_STATUS,
140 	PROP_VISIBILITY,
141 	PROP_TRANSPARENCY,
142 	PROP_UID,
143 	PROP_SEQUENCE,
144 	PROP_GUESTS_CAN_MODIFY,
145 	PROP_GUESTS_CAN_INVITE_OTHERS,
146 	PROP_GUESTS_CAN_SEE_GUESTS,
147 	PROP_ANYONE_CAN_ADD_SELF,
148 	PROP_RECURRENCE,
149 	PROP_ORIGINAL_EVENT_ID,
150 	PROP_ORIGINAL_EVENT_URI
151 };
152 
G_DEFINE_TYPE(GDataCalendarEvent,gdata_calendar_event,GDATA_TYPE_ENTRY)153 G_DEFINE_TYPE (GDataCalendarEvent, gdata_calendar_event, GDATA_TYPE_ENTRY)
154 
155 static void
156 gdata_calendar_event_class_init (GDataCalendarEventClass *klass)
157 {
158 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
159 	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
160 	GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
161 
162 	g_type_class_add_private (klass, sizeof (GDataCalendarEventPrivate));
163 
164 	gobject_class->constructor = gdata_calendar_event_constructor;
165 	gobject_class->get_property = gdata_calendar_event_get_property;
166 	gobject_class->set_property = gdata_calendar_event_set_property;
167 	gobject_class->dispose = gdata_calendar_event_dispose;
168 	gobject_class->finalize = gdata_calendar_event_finalize;
169 
170 	parsable_class->parse_json = parse_json;
171 	parsable_class->post_parse_json = post_parse_json;
172 	parsable_class->get_json = get_json;
173 	parsable_class->get_content_type = get_content_type;
174 
175 	entry_class->kind_term = "calendar#event";
176 
177 	/**
178 	 * GDataCalendarEvent:edited:
179 	 *
180 	 * The last time the event was edited. If the event has not been edited yet, the content indicates the time it was created.
181 	 *
182 	 * For more information, see the <ulink type="http" url="http://www.atomenabled.org/developers/protocol/#appEdited">
183 	 * Atom Publishing Protocol specification</ulink>.
184 	 */
185 	g_object_class_install_property (gobject_class, PROP_EDITED,
186 	                                 g_param_spec_int64 ("edited",
187 	                                                     "Edited", "The last time the event was edited.",
188 	                                                     -1, G_MAXINT64, -1,
189 	                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
190 
191 	/**
192 	 * GDataCalendarEvent:status:
193 	 *
194 	 * The scheduling status of the event. For example: %GDATA_GD_EVENT_STATUS_CANCELED or %GDATA_GD_EVENT_STATUS_CONFIRMED.
195 	 *
196 	 * For more information, see the <ulink type="http" url="http://code.google.com/apis/gdata/elements.html#gdEventStatus">
197 	 * GData specification</ulink>.
198 	 *
199 	 * Since: 0.2.0
200 	 */
201 	g_object_class_install_property (gobject_class, PROP_STATUS,
202 	                                 g_param_spec_string ("status",
203 	                                                      "Status", "The scheduling status of the event.",
204 	                                                      NULL,
205 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
206 
207 	/**
208 	 * GDataCalendarEvent:visibility:
209 	 *
210 	 * The event's visibility to calendar users. For example: %GDATA_GD_EVENT_VISIBILITY_PUBLIC or %GDATA_GD_EVENT_VISIBILITY_DEFAULT.
211 	 *
212 	 * For more information, see the <ulink type="http" url="http://code.google.com/apis/gdata/elements.html#gdVisibility">
213 	 * GData specification</ulink>.
214 	 */
215 	g_object_class_install_property (gobject_class, PROP_VISIBILITY,
216 	                                 g_param_spec_string ("visibility",
217 	                                                      "Visibility", "The event's visibility to calendar users.",
218 	                                                      NULL,
219 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
220 
221 	/**
222 	 * GDataCalendarEvent:transparency:
223 	 *
224 	 * How the event is marked as consuming time on a calendar. For example: %GDATA_GD_EVENT_TRANSPARENCY_OPAQUE or
225 	 * %GDATA_GD_EVENT_TRANSPARENCY_TRANSPARENT.
226 	 *
227 	 * For more information, see the <ulink type="http" url="http://code.google.com/apis/gdata/elements.html#gdTransparency">
228 	 * GData specification</ulink>.
229 	 */
230 	g_object_class_install_property (gobject_class, PROP_TRANSPARENCY,
231 	                                 g_param_spec_string ("transparency",
232 	                                                      "Transparency", "How the event is marked as consuming time on a calendar.",
233 	                                                      NULL,
234 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
235 
236 	/**
237 	 * GDataCalendarEvent:uid:
238 	 *
239 	 * The globally unique identifier (UID) of the event as defined in Section 4.8.4.7 of <ulink type="http"
240 	 * url="http://www.ietf.org/rfc/rfc2445.txt">RFC 2445</ulink>.
241 	 */
242 	g_object_class_install_property (gobject_class, PROP_UID,
243 	                                 g_param_spec_string ("uid",
244 	                                                      "UID", "The globally unique identifier (UID) of the event.",
245 	                                                      NULL,
246 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
247 
248 	/**
249 	 * GDataCalendarEvent:sequence:
250 	 *
251 	 * The revision sequence number of the event as defined in Section 4.8.7.4 of <ulink type="http"
252 	 * url="http://www.ietf.org/rfc/rfc2445.txt">RFC 2445</ulink>.
253 	 */
254 	g_object_class_install_property (gobject_class, PROP_SEQUENCE,
255 	                                 g_param_spec_uint ("sequence",
256 	                                                    "Sequence", "The revision sequence number of the event.",
257 	                                                    0, G_MAXUINT, 0,
258 	                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
259 
260 	/**
261 	 * GDataCalendarEvent:guests-can-modify:
262 	 *
263 	 * Indicates whether attendees may modify the original event, so that changes are visible to organizers and other attendees.
264 	 * Otherwise, any changes made by attendees will be restricted to that attendee's calendar.
265 	 *
266 	 * For more information, see the
267 	 * <ulink type="http" url="https://developers.google.com/google-apps/calendar/v3/reference/events#guestsCanInviteOthers">
268 	 * GData specification</ulink>.
269 	 */
270 	g_object_class_install_property (gobject_class, PROP_GUESTS_CAN_MODIFY,
271 	                                 g_param_spec_boolean ("guests-can-modify",
272 	                                                       "Guests can modify", "Indicates whether attendees may modify the original event.",
273 	                                                       FALSE,
274 	                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
275 
276 	/**
277 	 * GDataCalendarEvent:guests-can-invite-others:
278 	 *
279 	 * Indicates whether attendees may invite others to the event.
280 	 *
281 	 * For more information, see the <ulink type="http"
282 	 * url="https://developers.google.com/google-apps/calendar/v3/reference/events#guestsCanInviteOthers">GData specification</ulink>.
283 	 */
284 	g_object_class_install_property (gobject_class, PROP_GUESTS_CAN_INVITE_OTHERS,
285 	                                 g_param_spec_boolean ("guests-can-invite-others",
286 	                                                       "Guests can invite others", "Indicates whether attendees may invite others.",
287 	                                                       FALSE,
288 	                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
289 
290 	/**
291 	 * GDataCalendarEvent:guests-can-see-guests:
292 	 *
293 	 * Indicates whether attendees can see other people invited to the event.
294 	 *
295 	 * For more information, see the
296 	 * <ulink type="http" url="https://developers.google.com/google-apps/calendar/v3/reference/events#guestsCanSeeOtherGuests">
297 	 * GData specification</ulink>.
298 	 */
299 	g_object_class_install_property (gobject_class, PROP_GUESTS_CAN_SEE_GUESTS,
300 	                                 g_param_spec_boolean ("guests-can-see-guests",
301 	                                                       "Guests can see guests", "Indicates whether attendees can see other people invited.",
302 	                                                       FALSE,
303 	                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
304 
305 	/**
306 	 * GDataCalendarEvent:anyone-can-add-self:
307 	 *
308 	 * Indicates whether anyone can invite themselves to the event, by adding themselves to the attendee list.
309 	 */
310 	g_object_class_install_property (gobject_class, PROP_ANYONE_CAN_ADD_SELF,
311 	                                 g_param_spec_boolean ("anyone-can-add-self",
312 	                                                       "Anyone can add self", "Indicates whether anyone can invite themselves to the event.",
313 	                                                       FALSE,
314 	                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
315 
316 	/**
317 	 * GDataCalendarEvent:recurrence:
318 	 *
319 	 * Represents the dates and times when a recurring event takes place. The returned string is in iCal format, as a list of properties.
320 	 *
321 	 * For more information, see the <ulink type="http" url="http://code.google.com/apis/gdata/elements.html#gdRecurrence">
322 	 * GData specification</ulink>.
323 	 *
324 	 * Note: gdata_calendar_event_add_time() and gdata_calendar_event_set_recurrence() are mutually
325 	 * exclusive. See the documentation for gdata_calendar_event_add_time() for details.
326 	 *
327 	 * Since: 0.3.0
328 	 */
329 	g_object_class_install_property (gobject_class, PROP_RECURRENCE,
330 	                                 g_param_spec_string ("recurrence",
331 	                                                      "Recurrence", "Represents the dates and times when a recurring event takes place.",
332 	                                                      NULL,
333 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
334 
335 	/**
336 	 * GDataCalendarEvent:original-event-id:
337 	 *
338 	 * The event ID for the original event, if this event is an exception to a recurring event.
339 	 *
340 	 * Since: 0.3.0
341 	 */
342 	g_object_class_install_property (gobject_class, PROP_ORIGINAL_EVENT_ID,
343 	                                 g_param_spec_string ("original-event-id",
344 	                                                      "Original event ID", "The event ID for the original event.",
345 	                                                      NULL,
346 	                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
347 
348 	/**
349 	 * GDataCalendarEvent:original-event-uri:
350 	 *
351 	 * The event URI for the original event, if this event is an exception to a recurring event.
352 	 *
353 	 * Since: 0.3.0
354 	 */
355 	g_object_class_install_property (gobject_class, PROP_ORIGINAL_EVENT_URI,
356 	                                 g_param_spec_string ("original-event-uri",
357 	                                                      "Original event URI", "The event URI for the original event.",
358 	                                                      NULL,
359 	                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
360 }
361 
362 static void
gdata_calendar_event_init(GDataCalendarEvent * self)363 gdata_calendar_event_init (GDataCalendarEvent *self)
364 {
365 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_CALENDAR_EVENT, GDataCalendarEventPrivate);
366 	self->priv->edited = -1;
367 }
368 
369 static GObject *
gdata_calendar_event_constructor(GType type,guint n_construct_params,GObjectConstructParam * construct_params)370 gdata_calendar_event_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params)
371 {
372 	GObject *object;
373 
374 	/* Chain up to the parent class */
375 	object = G_OBJECT_CLASS (gdata_calendar_event_parent_class)->constructor (type, n_construct_params, construct_params);
376 
377 	if (_gdata_parsable_is_constructed_from_xml (GDATA_PARSABLE (object)) == FALSE) {
378 		GDataCalendarEventPrivate *priv = GDATA_CALENDAR_EVENT (object)->priv;
379 		GTimeVal time_val;
380 
381 		/* Set the edited property to the current time (creation time). We don't do this in *_init() since that would cause
382 		 * setting it from parse_xml() to fail (duplicate element). */
383 		g_get_current_time (&time_val);
384 		priv->edited = time_val.tv_sec;
385 	}
386 
387 	return object;
388 }
389 
390 static void
gdata_calendar_event_dispose(GObject * object)391 gdata_calendar_event_dispose (GObject *object)
392 {
393 	GDataCalendarEventPrivate *priv = GDATA_CALENDAR_EVENT (object)->priv;
394 
395 	g_list_free_full(priv->times, g_object_unref);
396 	priv->times = NULL;
397 
398 	g_list_free_full (priv->people, g_object_unref);
399 	priv->people = NULL;
400 
401 	g_list_free_full (priv->places, g_object_unref);
402 	priv->places = NULL;
403 
404 	/* Chain up to the parent class */
405 	G_OBJECT_CLASS (gdata_calendar_event_parent_class)->dispose (object);
406 }
407 
408 static void
gdata_calendar_event_finalize(GObject * object)409 gdata_calendar_event_finalize (GObject *object)
410 {
411 	GDataCalendarEventPrivate *priv = GDATA_CALENDAR_EVENT (object)->priv;
412 
413 	g_free (priv->status);
414 	g_free (priv->visibility);
415 	g_free (priv->transparency);
416 	g_free (priv->uid);
417 	g_free (priv->recurrence);
418 	g_free (priv->original_event_id);
419 	g_free (priv->original_event_uri);
420 	g_free (priv->organiser_email);
421 
422 	/* Chain up to the parent class */
423 	G_OBJECT_CLASS (gdata_calendar_event_parent_class)->finalize (object);
424 }
425 
426 static void
gdata_calendar_event_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)427 gdata_calendar_event_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
428 {
429 	GDataCalendarEventPrivate *priv = GDATA_CALENDAR_EVENT (object)->priv;
430 
431 	switch (property_id) {
432 		case PROP_EDITED:
433 			g_value_set_int64 (value, priv->edited);
434 			break;
435 		case PROP_STATUS:
436 			g_value_set_string (value, priv->status);
437 			break;
438 		case PROP_VISIBILITY:
439 			g_value_set_string (value, priv->visibility);
440 			break;
441 		case PROP_TRANSPARENCY:
442 			g_value_set_string (value, priv->transparency);
443 			break;
444 		case PROP_UID:
445 			g_value_set_string (value, priv->uid);
446 			break;
447 		case PROP_SEQUENCE:
448 			g_value_set_uint (value, CLAMP (priv->sequence, 0, G_MAXUINT));
449 			break;
450 		case PROP_GUESTS_CAN_MODIFY:
451 			g_value_set_boolean (value, priv->guests_can_modify);
452 			break;
453 		case PROP_GUESTS_CAN_INVITE_OTHERS:
454 			g_value_set_boolean (value, priv->guests_can_invite_others);
455 			break;
456 		case PROP_GUESTS_CAN_SEE_GUESTS:
457 			g_value_set_boolean (value, priv->guests_can_see_guests);
458 			break;
459 		case PROP_ANYONE_CAN_ADD_SELF:
460 			g_value_set_boolean (value, priv->anyone_can_add_self);
461 			break;
462 		case PROP_RECURRENCE:
463 			g_value_set_string (value, priv->recurrence);
464 			break;
465 		case PROP_ORIGINAL_EVENT_ID:
466 			g_value_set_string (value, priv->original_event_id);
467 			break;
468 		case PROP_ORIGINAL_EVENT_URI:
469 			g_value_set_string (value, priv->original_event_uri);
470 			break;
471 		default:
472 			/* We don't have any other property... */
473 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
474 			break;
475 	}
476 }
477 
478 static void
gdata_calendar_event_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)479 gdata_calendar_event_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
480 {
481 	GDataCalendarEvent *self = GDATA_CALENDAR_EVENT (object);
482 
483 	switch (property_id) {
484 		case PROP_STATUS:
485 			gdata_calendar_event_set_status (self, g_value_get_string (value));
486 			break;
487 		case PROP_VISIBILITY:
488 			gdata_calendar_event_set_visibility (self, g_value_get_string (value));
489 			break;
490 		case PROP_TRANSPARENCY:
491 			gdata_calendar_event_set_transparency (self, g_value_get_string (value));
492 			break;
493 		case PROP_UID:
494 			gdata_calendar_event_set_uid (self, g_value_get_string (value));
495 			break;
496 		case PROP_SEQUENCE:
497 			gdata_calendar_event_set_sequence (self, g_value_get_uint (value));
498 			break;
499 		case PROP_GUESTS_CAN_MODIFY:
500 			gdata_calendar_event_set_guests_can_modify (self, g_value_get_boolean (value));
501 			break;
502 		case PROP_GUESTS_CAN_INVITE_OTHERS:
503 			gdata_calendar_event_set_guests_can_invite_others (self, g_value_get_boolean (value));
504 			break;
505 		case PROP_GUESTS_CAN_SEE_GUESTS:
506 			gdata_calendar_event_set_guests_can_see_guests (self, g_value_get_boolean (value));
507 			break;
508 		case PROP_ANYONE_CAN_ADD_SELF:
509 			gdata_calendar_event_set_anyone_can_add_self (self, g_value_get_boolean (value));
510 			break;
511 		case PROP_RECURRENCE:
512 			gdata_calendar_event_set_recurrence (self, g_value_get_string (value));
513 			break;
514 		default:
515 			/* We don't have any other property... */
516 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
517 			break;
518 	}
519 }
520 
521 static gboolean
date_object_from_json(JsonReader * reader,const gchar * member_name,GDataParserOptions options,gint64 * date_time_output,gboolean * is_date_output,gboolean * success,GError ** error)522 date_object_from_json (JsonReader *reader,
523                        const gchar *member_name,
524                        GDataParserOptions options,
525                        gint64 *date_time_output,
526                        gboolean *is_date_output,
527                        gboolean *success,
528                        GError **error)
529 {
530 	gint64 date_time;
531 	gboolean is_date = FALSE;
532 	gboolean found_member = FALSE;
533 
534 	/* Check if there’s such an element */
535 	if (g_strcmp0 (json_reader_get_member_name (reader), member_name) != 0) {
536 		return FALSE;
537 	}
538 
539 	/* Check that it’s an object. */
540 	if (!json_reader_is_object (reader)) {
541 		const GError *child_error;
542 
543 		/* Manufacture an error. */
544 		json_reader_read_member (reader, "dateTime");
545 		child_error = json_reader_get_error (reader);
546 		g_assert (child_error != NULL);
547 		*success = gdata_parser_error_from_json_error (reader,
548 		                                               child_error,
549 		                                               error);
550 		json_reader_end_member (reader);
551 
552 		return TRUE;
553 	}
554 
555 	/* Try to parse either the dateTime or date member. */
556 	if (json_reader_read_member (reader, "dateTime")) {
557 		const gchar *date_string;
558 		const GError *child_error;
559 		GTimeVal time_val;
560 
561 		date_string = json_reader_get_string_value (reader);
562 		child_error = json_reader_get_error (reader);
563 
564 		if (child_error != NULL) {
565 			*success = gdata_parser_error_from_json_error (reader,
566 			                                               child_error,
567 			                                               error);
568 			json_reader_end_member (reader);
569 			return TRUE;
570 		}
571 
572 		if (!g_time_val_from_iso8601 (date_string, &time_val)) {
573 			*success = gdata_parser_error_not_iso8601_format_json (reader, date_string, error);
574 			json_reader_end_member (reader);
575 			return TRUE;
576 		}
577 
578 		date_time = time_val.tv_sec;
579 		is_date = FALSE;
580 		found_member = TRUE;
581 	}
582 	json_reader_end_member (reader);
583 
584 	if (json_reader_read_member (reader, "date")) {
585 		const gchar *date_string;
586 		const GError *child_error;
587 
588 		date_string = json_reader_get_string_value (reader);
589 		child_error = json_reader_get_error (reader);
590 
591 		if (child_error != NULL) {
592 			*success = gdata_parser_error_from_json_error (reader,
593 			                                               child_error,
594 			                                               error);
595 			json_reader_end_member (reader);
596 			return TRUE;
597 		}
598 
599 		if (!gdata_parser_int64_from_date (date_string, &date_time)) {
600 			*success = gdata_parser_error_not_iso8601_format_json (reader, date_string, error);
601 			json_reader_end_member (reader);
602 			return TRUE;
603 		}
604 
605 		is_date = TRUE;
606 		found_member = TRUE;
607 	}
608 	json_reader_end_member (reader);
609 
610 	/* Ignore timeZone; it should be specified in dateTime. */
611 	if (!found_member) {
612 		*success = gdata_parser_error_required_json_content_missing (reader, error);
613 		return TRUE;
614 	}
615 
616 	*date_time_output = date_time;
617 	*is_date_output = is_date;
618 	*success = TRUE;
619 
620 	return TRUE;
621 }
622 
623 /* Convert between v2 and v3 versions of various enum values. v2 uses a URI
624  * style with a constant prefix; v3 simply drops this prefix, and changes the
625  * spelling of ‘canceled’ to ‘cancelled’. */
626 #define V2_PREFIX "http://schemas.google.com/g/2005#event."
627 
628 static gchar *
add_v2_prefix(const gchar * in)629 add_v2_prefix (const gchar *in)
630 {
631 	return g_strconcat (V2_PREFIX, in, NULL);
632 }
633 
634 static const gchar *
strip_v2_prefix(const gchar * uri)635 strip_v2_prefix (const gchar *uri)
636 {
637 	/* Convert to v3 format. */
638 	if (g_str_has_prefix (uri, V2_PREFIX)) {
639 		return uri + strlen (V2_PREFIX);
640 	} else {
641 		return uri;
642 	}
643 }
644 
645 static gboolean
parse_json(GDataParsable * parsable,JsonReader * reader,gpointer user_data,GError ** error)646 parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
647 {
648 	gboolean success;
649 	GDataCalendarEvent *self = GDATA_CALENDAR_EVENT (parsable);
650 	GDataCalendarEventPrivate *priv = self->priv;
651 
652 	/* FIXME: Currently unsupported:
653 	 *  - htmlLink
654 	 *  - colorId
655 	 *  - endTimeUnspecified
656 	 *  - originalStartTime
657 	 *  - attendeesOmitted
658 	 *  - extendedProperties
659 	 *  - hangoutLink
660 	 *  - gadget
661 	 *  - privateCopy
662 	 *  - locked
663 	 *  - reminders
664 	 *  - source
665 	 */
666 
667 	if (g_strcmp0 (json_reader_get_member_name (reader), "start") == 0) {
668 		self->priv->parser.seen_start = TRUE;
669 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "end") == 0) {
670 		self->priv->parser.seen_end = TRUE;
671 	}
672 
673 	if (gdata_parser_string_from_json_member (reader, "recurringEventId", P_DEFAULT, &self->priv->original_event_id, &success, error) ||
674 	    gdata_parser_boolean_from_json_member (reader, "guestsCanModify", P_DEFAULT, &self->priv->guests_can_modify, &success, error) ||
675 	    gdata_parser_boolean_from_json_member (reader, "guestsCanInviteOthers", P_DEFAULT, &self->priv->guests_can_invite_others, &success, error) ||
676 	    gdata_parser_boolean_from_json_member (reader, "guestsCanSeeOtherGuests", P_DEFAULT, &self->priv->guests_can_see_guests, &success, error) ||
677 	    gdata_parser_boolean_from_json_member (reader, "anyoneCanAddSelf", P_DEFAULT, &self->priv->anyone_can_add_self, &success, error) ||
678 	    gdata_parser_string_from_json_member (reader, "iCalUID", P_DEFAULT, &self->priv->uid, &success, error) ||
679 	    gdata_parser_int_from_json_member (reader, "sequence", P_DEFAULT, &self->priv->sequence, &success, error) ||
680 	    gdata_parser_int64_time_from_json_member (reader, "updated", P_DEFAULT, &self->priv->edited, &success, error) ||
681 	    date_object_from_json (reader, "start", P_DEFAULT, &self->priv->parser.start_time, &self->priv->parser.start_is_date, &success, error) ||
682 	    date_object_from_json (reader, "end", P_DEFAULT, &self->priv->parser.end_time, &self->priv->parser.end_is_date, &success, error)) {
683 		if (success) {
684 			if (self->priv->edited != -1) {
685 				_gdata_entry_set_updated (GDATA_ENTRY (parsable),
686 				                          self->priv->edited);
687 			}
688 
689 			if (self->priv->original_event_id != NULL) {
690 				g_free (self->priv->original_event_uri);
691 				self->priv->original_event_uri = g_strconcat ("https://www.googleapis.com/calendar/v3/events/",
692 				                                              self->priv->original_event_id, NULL);
693 			}
694 
695 			if (self->priv->parser.seen_start && self->priv->parser.seen_end) {
696 				GDataGDWhen *when;
697 
698 				when = gdata_gd_when_new (self->priv->parser.start_time,
699 				                          self->priv->parser.end_time,
700 				                          self->priv->parser.start_is_date ||
701 				                          self->priv->parser.end_is_date);
702 				self->priv->times = g_list_prepend (self->priv->times, when);  /* transfer ownership */
703 
704 				self->priv->parser.seen_start = FALSE;
705 				self->priv->parser.seen_end = FALSE;
706 			}
707 		}
708 
709 		return success;
710 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "transparency") == 0) {
711 		gchar *transparency = NULL;  /* owned */
712 
713 		g_assert (gdata_parser_string_from_json_member (reader,
714 		                                                "transparency",
715 		                                                P_DEFAULT,
716 		                                                &transparency,
717 		                                                &success,
718 		                                                error));
719 
720 		if (success) {
721 			priv->transparency = add_v2_prefix (transparency);
722 		}
723 
724 		g_free (transparency);
725 
726 		return success;
727 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "visibility") == 0) {
728 		gchar *visibility = NULL;  /* owned */
729 
730 		g_assert (gdata_parser_string_from_json_member (reader,
731 		                                                "visibility",
732 		                                                P_DEFAULT,
733 		                                                &visibility,
734 		                                                &success,
735 		                                                error));
736 
737 		if (success) {
738 			priv->visibility = add_v2_prefix (visibility);
739 		}
740 
741 		g_free (visibility);
742 
743 		return success;
744 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "status") == 0) {
745 		gchar *status = NULL;  /* owned */
746 
747 		g_assert (gdata_parser_string_from_json_member (reader,
748 		                                                "status",
749 		                                                P_DEFAULT,
750 		                                                &status,
751 		                                                &success,
752 		                                                error));
753 
754 		if (success) {
755 			if (g_strcmp0 (status, "cancelled") == 0) {
756 				/* Those damned British Englishes. */
757 				priv->status = add_v2_prefix ("canceled");
758 			} else {
759 				priv->status = add_v2_prefix (status);
760 			}
761 		}
762 
763 		g_free (status);
764 
765 		return success;
766 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "summary") == 0) {
767 		const gchar *summary;
768 		const GError *child_error = NULL;
769 
770 		summary = json_reader_get_string_value (reader);
771 		child_error = json_reader_get_error (reader);
772 
773 		if (child_error != NULL) {
774 			gdata_parser_error_from_json_error (reader,
775 			                                    child_error, error);
776 			return FALSE;
777 		}
778 
779 		gdata_entry_set_title (GDATA_ENTRY (parsable), summary);
780 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "description") == 0) {
781 		const gchar *description;
782 		const GError *child_error = NULL;
783 
784 		description = json_reader_get_string_value (reader);
785 		child_error = json_reader_get_error (reader);
786 
787 		if (child_error != NULL) {
788 			gdata_parser_error_from_json_error (reader,
789 			                                    child_error, error);
790 			return FALSE;
791 		}
792 
793 		gdata_entry_set_content (GDATA_ENTRY (parsable), description);
794 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "location") == 0) {
795 		const gchar *location;
796 		GDataGDWhere *where = NULL;  /* owned */
797 		const GError *child_error = NULL;
798 
799 		location = json_reader_get_string_value (reader);
800 		child_error = json_reader_get_error (reader);
801 
802 		if (child_error != NULL) {
803 			gdata_parser_error_from_json_error (reader,
804 			                                    child_error, error);
805 			return FALSE;
806 		}
807 
808 		where = gdata_gd_where_new (GDATA_GD_WHERE_EVENT,
809 		                            location, NULL);
810 		priv->places = g_list_prepend (priv->places, where);  /* transfer ownership */
811 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "created") == 0) {
812 		gint64 created;
813 
814 		g_assert (gdata_parser_int64_time_from_json_member (reader,
815 		                                                    "created",
816 		                                                    P_DEFAULT,
817 		                                                    &created,
818 		                                                    &success,
819 		                                                    error));
820 
821 		if (success) {
822 			_gdata_entry_set_published (GDATA_ENTRY (parsable),
823 			                            created);
824 		}
825 
826 		return success;
827 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "recurrence") == 0) {
828 		guint i, j;
829 		GString *recurrence = NULL;  /* owned */
830 
831 		/* In the JSON API, the recurrence is given as an array of
832 		 * strings, each giving an RFC 2445 property such as RRULE,
833 		 * EXRULE, RDATE or EXDATE. Concatenate them all to form a
834 		 * recurrence string as used in v2 of the API. */
835 		if (self->priv->recurrence != NULL) {
836 			return gdata_parser_error_duplicate_json_element (reader,
837 			                                                  error);
838 		}
839 
840 		recurrence = g_string_new ("");
841 
842 		for (i = 0, j = json_reader_count_elements (reader); i < j; i++) {
843 			const gchar *line;
844 			const GError *child_error;
845 
846 			json_reader_read_element (reader, i);
847 
848 			line = json_reader_get_string_value (reader);
849 			child_error = json_reader_get_error (reader);
850 			if (child_error != NULL) {
851 				gdata_parser_error_from_json_error (reader, child_error, error);
852 				json_reader_end_element (reader);
853 				return FALSE;
854 			}
855 
856 			g_string_append (recurrence, line);
857 			g_string_append (recurrence, "\n");
858 
859 			json_reader_end_element (reader);
860 		}
861 
862 		g_assert (self->priv->recurrence == NULL);
863 		self->priv->recurrence = g_string_free (recurrence, FALSE);
864 
865 		return TRUE;
866 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "attendees") == 0) {
867 		guint i, j;
868 
869 		if (priv->people != NULL) {
870 			return gdata_parser_error_duplicate_json_element (reader,
871 			                                                  error);
872 		}
873 
874 		for (i = 0, j = json_reader_count_elements (reader); i < j; i++) {
875 			GDataGDWho *who = NULL;  /* owned */
876 			const gchar *email_address, *value_string;
877 			const gchar *relation_type;
878 			gboolean is_organizer, is_resource;
879 			const GError *child_error;
880 
881 			json_reader_read_element (reader, i);
882 
883 			json_reader_read_member (reader, "responseStatus");
884 			child_error = json_reader_get_error (reader);
885 			if (child_error != NULL) {
886 				gdata_parser_error_from_json_error (reader,
887 				                                    child_error,
888 				                                    error);
889 				json_reader_end_member (reader);
890 				return FALSE;
891 			}
892 			json_reader_end_member (reader);
893 
894 			json_reader_read_member (reader, "email");
895 			email_address = json_reader_get_string_value (reader);
896 			json_reader_end_member (reader);
897 
898 			json_reader_read_member (reader, "displayName");
899 			value_string = json_reader_get_string_value (reader);
900 			json_reader_end_member (reader);
901 
902 			json_reader_read_member (reader, "organizer");
903 			is_organizer = json_reader_get_boolean_value (reader);
904 			json_reader_end_member (reader);
905 
906 			json_reader_read_member (reader, "resource");
907 			is_resource = json_reader_get_boolean_value (reader);
908 			json_reader_end_member (reader);
909 
910 			/* FIXME: Currently unsupported:
911 			 *  - id
912 			 *  - self
913 			 *  - optional (writeble)
914 			 *  - responseStatus (writeble)
915 			 *  - comment (writeble)
916 			 *  - additionalGuests (writeble)
917 			 */
918 
919 			if (is_organizer) {
920 				relation_type = GDATA_GD_WHO_EVENT_ORGANIZER;
921 			} else if (!is_resource) {
922 				relation_type = GDATA_GD_WHO_EVENT_ATTENDEE;
923 			} else {
924 				/* FIXME: Add support for resources. */
925 				relation_type = NULL;
926 			}
927 
928 			who = gdata_gd_who_new (relation_type, value_string,
929 			                        email_address);
930 			priv->people = g_list_prepend (priv->people, who);  /* transfer ownership */
931 
932 			json_reader_end_element (reader);
933 		}
934 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "organizer") == 0) {
935 		/* This actually gives the parent calendar. Optional. */
936 		g_clear_pointer (&priv->organiser_email, g_free);
937 		if (json_reader_read_member (reader, "email"))
938 			priv->organiser_email = g_strdup (json_reader_get_string_value (reader));
939 		json_reader_end_member (reader);
940 
941 		return TRUE;
942 	} else if (g_strcmp0 (json_reader_get_member_name (reader), "creator") == 0) {
943 		/* These are read-only and already handled as part of
944 		 * ‘attendees’, so ignore them. */
945 		return TRUE;
946 	} else {
947 		return GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->parse_json (parsable, reader, user_data, error);
948 	}
949 
950 	return TRUE;
951 }
952 
953 static gboolean
post_parse_json(GDataParsable * parsable,gpointer user_data,GError ** error)954 post_parse_json (GDataParsable *parsable, gpointer user_data, GError **error)
955 {
956 	GDataLink *_link = NULL;  /* owned */
957 	const gchar *id, *calendar_id;
958 	gchar *uri = NULL;  /* owned */
959 	GDataCalendarEventPrivate *priv;
960 
961 	priv = GDATA_CALENDAR_EVENT (parsable)->priv;
962 
963 	/* Set the self link, which is needed for gdata_service_delete_entry().
964 	 * Unfortunately, it needs the event ID _and_ the calendar ID — which
965 	 * is perversely only available as the organiser e-mail address. */
966 	id = gdata_entry_get_id (GDATA_ENTRY (parsable));
967 	calendar_id = priv->organiser_email;
968 
969 	if (id == NULL || calendar_id == NULL) {
970 		return TRUE;
971 	}
972 
973 	uri = g_strconcat ("https://www.googleapis.com/calendar/v3/calendars/",
974 	                   calendar_id, "/events/", id, NULL);
975 	_link = gdata_link_new (uri, GDATA_LINK_SELF);
976 	gdata_entry_add_link (GDATA_ENTRY (parsable), _link);
977 	g_object_unref (_link);
978 	g_free (uri);
979 
980 	return TRUE;
981 }
982 
983 static void
get_json(GDataParsable * parsable,JsonBuilder * builder)984 get_json (GDataParsable *parsable, JsonBuilder *builder)
985 {
986 	GList *l;
987 	const gchar *id, *etag, *title, *description;
988 	GDataGDWho *organiser_who = NULL;  /* unowned */
989 	GDataCalendarEventPrivate *priv = GDATA_CALENDAR_EVENT (parsable)->priv;
990 
991 	/* FIXME: Support:
992 	 *  - colorId
993 	 *  - attendeesOmitted
994 	 *  - extendedProperties
995 	 *  - gadget
996 	 *  - reminders
997 	 *  - source
998 	 */
999 
1000 	id = gdata_entry_get_id (GDATA_ENTRY (parsable));
1001 	if (id != NULL) {
1002 		json_builder_set_member_name (builder, "id");
1003 		json_builder_add_string_value (builder, id);
1004 	}
1005 
1006 	json_builder_set_member_name (builder, "kind");
1007 	json_builder_add_string_value (builder, "calendar#event");
1008 
1009 	/* Add the ETag, if available. */
1010 	etag = gdata_entry_get_etag (GDATA_ENTRY (parsable));
1011 	if (etag != NULL) {
1012 		json_builder_set_member_name (builder, "etag");
1013 		json_builder_add_string_value (builder, etag);
1014 	}
1015 
1016 	/* Calendar labels titles as ‘summary’. */
1017 	title = gdata_entry_get_title (GDATA_ENTRY (parsable));
1018 	if (title != NULL) {
1019 		json_builder_set_member_name (builder, "summary");
1020 		json_builder_add_string_value (builder, title);
1021 	}
1022 
1023 	description = gdata_entry_get_content (GDATA_ENTRY (parsable));
1024 	if (description != NULL) {
1025 		json_builder_set_member_name (builder, "description");
1026 		json_builder_add_string_value (builder, description);
1027 	}
1028 
1029 	/* Add all the calendar-specific JSON */
1030 	json_builder_set_member_name (builder, "anyoneCanAddSelf");
1031 	json_builder_add_boolean_value (builder, priv->anyone_can_add_self);
1032 
1033 	json_builder_set_member_name (builder, "guestsCanInviteOthers");
1034 	json_builder_add_boolean_value (builder, priv->guests_can_invite_others);
1035 
1036 	json_builder_set_member_name (builder, "guestsCanModify");
1037 	json_builder_add_boolean_value (builder, priv->guests_can_modify);
1038 
1039 	json_builder_set_member_name (builder, "guestsCanSeeOtherGuests");
1040 	json_builder_add_boolean_value (builder, priv->guests_can_see_guests);
1041 
1042 	if (priv->transparency != NULL) {
1043 		json_builder_set_member_name (builder, "transparency");
1044 		json_builder_add_string_value (builder,
1045 		                               strip_v2_prefix (priv->transparency));
1046 	}
1047 
1048 	if (priv->visibility != NULL) {
1049 		json_builder_set_member_name (builder, "visibility");
1050 		json_builder_add_string_value (builder,
1051 		                               strip_v2_prefix (priv->visibility));
1052 	}
1053 
1054 	if (priv->uid != NULL) {
1055 		json_builder_set_member_name (builder, "iCalUID");
1056 		json_builder_add_string_value (builder, priv->uid);
1057 	}
1058 
1059 	if (priv->sequence > 0) {
1060 		json_builder_set_member_name (builder, "sequence");
1061 		json_builder_add_int_value (builder, priv->sequence);
1062 	}
1063 
1064 	if (priv->status != NULL) {
1065 		const gchar *status;
1066 
1067 		/* Convert to v3 format. */
1068 		status = strip_v2_prefix (priv->status);
1069 		if (g_strcmp0 (status, "canceled") == 0) {
1070 			status = "cancelled";
1071 		}
1072 
1073 		json_builder_set_member_name (builder, "status");
1074 		json_builder_add_string_value (builder, status);
1075 	}
1076 
1077 	if (priv->recurrence != NULL) {
1078 		gchar **parts;
1079 		guint i;
1080 
1081 		json_builder_set_member_name (builder, "recurrence");
1082 		json_builder_begin_array (builder);
1083 
1084 		parts = g_strsplit (priv->recurrence, "\n", -1);
1085 
1086 		for (i = 0; parts[i] != NULL; i++) {
1087 			json_builder_add_string_value (builder, parts[i]);
1088 		}
1089 
1090 		g_strfreev (parts);
1091 
1092 		json_builder_end_array (builder);
1093 	}
1094 
1095 	if (priv->original_event_id != NULL) {
1096 		json_builder_set_member_name (builder, "recurringEventId");
1097 		json_builder_add_string_value (builder, priv->original_event_id);
1098 	}
1099 
1100 	/* Times. */
1101 	for (l = priv->times; l != NULL; l = l->next) {
1102 		GDataGDWhen *when;  /* unowned */
1103 		gchar *val = NULL;  /* owned */
1104 		const gchar *member_name;
1105 		gint64 start_time, end_time;
1106 
1107 		when = l->data;
1108 
1109 		/* Start time. */
1110 		start_time = gdata_gd_when_get_start_time (when);
1111 		json_builder_set_member_name (builder, "start");
1112 		json_builder_begin_object (builder);
1113 
1114 		if (gdata_gd_when_is_date (when)) {
1115 			member_name = "date";
1116 			val = gdata_parser_date_from_int64 (start_time);
1117 		} else {
1118 			member_name = "dateTime";
1119 			val = gdata_parser_int64_to_iso8601 (start_time);
1120 		}
1121 
1122 		json_builder_set_member_name (builder, member_name);
1123 		json_builder_add_string_value (builder, val);
1124 		g_free (val);
1125 
1126 		json_builder_set_member_name (builder, "timeZone");
1127 		json_builder_add_string_value (builder, "UTC");
1128 
1129 		json_builder_end_object (builder);
1130 
1131 		/* End time. */
1132 		end_time = gdata_gd_when_get_end_time (when);
1133 
1134 		if (end_time > -1) {
1135 			json_builder_set_member_name (builder, "end");
1136 			json_builder_begin_object (builder);
1137 
1138 			if (gdata_gd_when_is_date (when)) {
1139 				member_name = "date";
1140 				val = gdata_parser_date_from_int64 (end_time);
1141 			} else {
1142 				member_name = "dateTime";
1143 				val = gdata_parser_int64_to_iso8601 (end_time);
1144 			}
1145 
1146 			json_builder_set_member_name (builder, member_name);
1147 			json_builder_add_string_value (builder, val);
1148 			g_free (val);
1149 
1150 			json_builder_set_member_name (builder, "timeZone");
1151 			json_builder_add_string_value (builder, "UTC");
1152 
1153 			json_builder_end_object (builder);
1154 		} else {
1155 			json_builder_set_member_name (builder, "endTimeUnspecified");
1156 			json_builder_add_boolean_value (builder, TRUE);
1157 		}
1158 
1159 		/* Only use the first time. :-(
1160 		 * FIXME: There must be a better solution. */
1161 		if (l->next != NULL) {
1162 			g_warning ("Ignoring secondary times; they are no "
1163 			           "longer supported by the server-side API.");
1164 			break;
1165 		}
1166 	}
1167 
1168 	/* Locations. */
1169 	for (l = priv->places; l != NULL; l = l->next) {
1170 		GDataGDWhere *where;  /* unowned */
1171 		const gchar *location;
1172 
1173 		where = l->data;
1174 		location = gdata_gd_where_get_value_string (where);
1175 
1176 		json_builder_set_member_name (builder, "location");
1177 		json_builder_add_string_value (builder, location);
1178 
1179 		/* Only use the first location. :-(
1180 		 * FIXME: There must be a better solution. */
1181 		if (l->next != NULL) {
1182 			g_warning ("Ignoring secondary locations; they are no "
1183 			           "longer supported by the server-side API.");
1184 			break;
1185 		}
1186 	}
1187 
1188 	/* People. */
1189 	json_builder_set_member_name (builder, "attendees");
1190 	json_builder_begin_array (builder);
1191 
1192 	for (l = priv->people; l != NULL; l = l->next) {
1193 		GDataGDWho *who;  /* unowned */
1194 		const gchar *display_name, *email_address;
1195 
1196 		who = l->data;
1197 
1198 		json_builder_begin_object (builder);
1199 
1200 		display_name = gdata_gd_who_get_value_string (who);
1201 		if (display_name != NULL) {
1202 			json_builder_set_member_name (builder, "displayName");
1203 			json_builder_add_string_value (builder, display_name);
1204 		}
1205 
1206 		email_address = gdata_gd_who_get_email_address (who);
1207 		if (email_address != NULL) {
1208 			json_builder_set_member_name (builder, "email");
1209 			json_builder_add_string_value (builder, email_address);
1210 		}
1211 
1212 		if (g_strcmp0 (gdata_gd_who_get_relation_type (who),
1213 		               GDATA_GD_WHO_EVENT_ORGANIZER) == 0) {
1214 			json_builder_set_member_name (builder, "organizer");
1215 			json_builder_add_boolean_value (builder, TRUE);
1216 
1217 			organiser_who = who;
1218 		}
1219 
1220 		json_builder_end_object (builder);
1221 	}
1222 
1223 	json_builder_end_array (builder);
1224 
1225 	if (organiser_who != NULL) {
1226 		const gchar *display_name, *email_address;
1227 
1228 		json_builder_set_member_name (builder, "organizer");
1229 		json_builder_begin_object (builder);
1230 
1231 		display_name = gdata_gd_who_get_value_string (organiser_who);
1232 		if (display_name != NULL) {
1233 			json_builder_set_member_name (builder, "displayName");
1234 			json_builder_add_string_value (builder, display_name);
1235 		}
1236 
1237 		email_address = gdata_gd_who_get_email_address (organiser_who);
1238 		if (email_address != NULL) {
1239 			json_builder_set_member_name (builder, "email");
1240 			json_builder_add_string_value (builder, email_address);
1241 		}
1242 
1243 		json_builder_end_object (builder);
1244 	}
1245 }
1246 
1247 static const gchar *
get_content_type(void)1248 get_content_type (void)
1249 {
1250 	return "application/json";
1251 }
1252 
1253 /**
1254  * gdata_calendar_event_new:
1255  * @id: (allow-none): the event's ID, or %NULL
1256  *
1257  * Creates a new #GDataCalendarEvent with the given ID and default properties.
1258  *
1259  * Return value: a new #GDataCalendarEvent; unref with g_object_unref()
1260  */
1261 GDataCalendarEvent *
gdata_calendar_event_new(const gchar * id)1262 gdata_calendar_event_new (const gchar *id)
1263 {
1264 	return GDATA_CALENDAR_EVENT (g_object_new (GDATA_TYPE_CALENDAR_EVENT, "id", id, NULL));
1265 }
1266 
1267 /**
1268  * gdata_calendar_event_get_edited:
1269  * @self: a #GDataCalendarEvent
1270  *
1271  * Gets the #GDataCalendarEvent:edited property. If the property is unset, <code class="literal">-1</code> will be returned.
1272  *
1273  * Return value: the UNIX timestamp for the time the event was last edited, or <code class="literal">-1</code>
1274  */
1275 gint64
gdata_calendar_event_get_edited(GDataCalendarEvent * self)1276 gdata_calendar_event_get_edited (GDataCalendarEvent *self)
1277 {
1278 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), -1);
1279 	return self->priv->edited;
1280 }
1281 
1282 /**
1283  * gdata_calendar_event_get_status:
1284  * @self: a #GDataCalendarEvent
1285  *
1286  * Gets the #GDataCalendarEvent:status property.
1287  *
1288  * Return value: the event status, or %NULL
1289  *
1290  * Since: 0.2.0
1291  */
1292 const gchar *
gdata_calendar_event_get_status(GDataCalendarEvent * self)1293 gdata_calendar_event_get_status (GDataCalendarEvent *self)
1294 {
1295 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), NULL);
1296 	return self->priv->status;
1297 }
1298 
1299 /**
1300  * gdata_calendar_event_set_status:
1301  * @self: a #GDataCalendarEvent
1302  * @status: (allow-none): a new event status, or %NULL
1303  *
1304  * Sets the #GDataCalendarEvent:status property to the new status, @status.
1305  *
1306  * Set @status to %NULL to unset the property in the event.
1307  *
1308  * Since: 0.2.0
1309  */
1310 void
gdata_calendar_event_set_status(GDataCalendarEvent * self,const gchar * status)1311 gdata_calendar_event_set_status (GDataCalendarEvent *self, const gchar *status)
1312 {
1313 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1314 
1315 	g_free (self->priv->status);
1316 	self->priv->status = g_strdup (status);
1317 	g_object_notify (G_OBJECT (self), "status");
1318 }
1319 
1320 /**
1321  * gdata_calendar_event_get_visibility:
1322  * @self: a #GDataCalendarEvent
1323  *
1324  * Gets the #GDataCalendarEvent:visibility property.
1325  *
1326  * Return value: the event visibility, or %NULL
1327  */
1328 const gchar *
gdata_calendar_event_get_visibility(GDataCalendarEvent * self)1329 gdata_calendar_event_get_visibility (GDataCalendarEvent *self)
1330 {
1331 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), NULL);
1332 	return self->priv->visibility;
1333 }
1334 
1335 /**
1336  * gdata_calendar_event_set_visibility:
1337  * @self: a #GDataCalendarEvent
1338  * @visibility: (allow-none): a new event visibility, or %NULL
1339  *
1340  * Sets the #GDataCalendarEvent:visibility property to the new visibility, @visibility.
1341  *
1342  * Set @visibility to %NULL to unset the property in the event.
1343  */
1344 void
gdata_calendar_event_set_visibility(GDataCalendarEvent * self,const gchar * visibility)1345 gdata_calendar_event_set_visibility (GDataCalendarEvent *self, const gchar *visibility)
1346 {
1347 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1348 
1349 	g_free (self->priv->visibility);
1350 	self->priv->visibility = g_strdup (visibility);
1351 	g_object_notify (G_OBJECT (self), "visibility");
1352 }
1353 
1354 /**
1355  * gdata_calendar_event_get_transparency:
1356  * @self: a #GDataCalendarEvent
1357  *
1358  * Gets the #GDataCalendarEvent:transparency property.
1359  *
1360  * Return value: the event transparency, or %NULL
1361  */
1362 const gchar *
gdata_calendar_event_get_transparency(GDataCalendarEvent * self)1363 gdata_calendar_event_get_transparency (GDataCalendarEvent *self)
1364 {
1365 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), NULL);
1366 	return self->priv->transparency;
1367 }
1368 
1369 /**
1370  * gdata_calendar_event_set_transparency:
1371  * @self: a #GDataCalendarEvent
1372  * @transparency: (allow-none): a new event transparency, or %NULL
1373  *
1374  * Sets the #GDataCalendarEvent:transparency property to the new transparency, @transparency.
1375  *
1376  * Set @transparency to %NULL to unset the property in the event.
1377  */
1378 void
gdata_calendar_event_set_transparency(GDataCalendarEvent * self,const gchar * transparency)1379 gdata_calendar_event_set_transparency (GDataCalendarEvent *self, const gchar *transparency)
1380 {
1381 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1382 
1383 	g_free (self->priv->transparency);
1384 	self->priv->transparency = g_strdup (transparency);
1385 	g_object_notify (G_OBJECT (self), "transparency");
1386 }
1387 
1388 /**
1389  * gdata_calendar_event_get_uid:
1390  * @self: a #GDataCalendarEvent
1391  *
1392  * Gets the #GDataCalendarEvent:uid property.
1393  *
1394  * Return value: the event's UID, or %NULL
1395  */
1396 const gchar *
gdata_calendar_event_get_uid(GDataCalendarEvent * self)1397 gdata_calendar_event_get_uid (GDataCalendarEvent *self)
1398 {
1399 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), NULL);
1400 	return self->priv->uid;
1401 }
1402 
1403 /**
1404  * gdata_calendar_event_set_uid:
1405  * @self: a #GDataCalendarEvent
1406  * @uid: (allow-none): a new event UID, or %NULL
1407  *
1408  * Sets the #GDataCalendarEvent:uid property to the new UID, @uid.
1409  *
1410  * Set @uid to %NULL to unset the property in the event.
1411  */
1412 void
gdata_calendar_event_set_uid(GDataCalendarEvent * self,const gchar * uid)1413 gdata_calendar_event_set_uid (GDataCalendarEvent *self, const gchar *uid)
1414 {
1415 	/* TODO: is modifying this allowed? */
1416 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1417 
1418 	g_free (self->priv->uid);
1419 	self->priv->uid = g_strdup (uid);
1420 	g_object_notify (G_OBJECT (self), "uid");
1421 }
1422 
1423 /**
1424  * gdata_calendar_event_get_sequence:
1425  * @self: a #GDataCalendarEvent
1426  *
1427  * Gets the #GDataCalendarEvent:sequence property.
1428  *
1429  * Return value: the event's sequence number
1430  */
1431 guint
gdata_calendar_event_get_sequence(GDataCalendarEvent * self)1432 gdata_calendar_event_get_sequence (GDataCalendarEvent *self)
1433 {
1434 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), 0);
1435 	return CLAMP (self->priv->sequence, 0, G_MAXUINT);
1436 }
1437 
1438 /**
1439  * gdata_calendar_event_set_sequence:
1440  * @self: a #GDataCalendarEvent
1441  * @sequence: a new sequence number, or <code class="literal">0</code>
1442  *
1443  * Sets the #GDataCalendarEvent:sequence property to the new sequence number, @sequence.
1444  */
1445 void
gdata_calendar_event_set_sequence(GDataCalendarEvent * self,guint sequence)1446 gdata_calendar_event_set_sequence (GDataCalendarEvent *self, guint sequence)
1447 {
1448 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1449 	self->priv->sequence = sequence;
1450 	g_object_notify (G_OBJECT (self), "sequence");
1451 }
1452 
1453 /**
1454  * gdata_calendar_event_get_guests_can_modify:
1455  * @self: a #GDataCalendarEvent
1456  *
1457  * Gets the #GDataCalendarEvent:guests-can-modify property.
1458  *
1459  * Return value: %TRUE if attendees can modify the original event, %FALSE otherwise
1460  */
1461 gboolean
gdata_calendar_event_get_guests_can_modify(GDataCalendarEvent * self)1462 gdata_calendar_event_get_guests_can_modify (GDataCalendarEvent *self)
1463 {
1464 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), FALSE);
1465 	return self->priv->guests_can_modify;
1466 }
1467 
1468 /**
1469  * gdata_calendar_event_set_guests_can_modify:
1470  * @self: a #GDataCalendarEvent
1471  * @guests_can_modify: %TRUE if attendees can modify the original event, %FALSE otherwise
1472  *
1473  * Sets the #GDataCalendarEvent:guests-can-modify property to @guests_can_modify.
1474  */
1475 void
gdata_calendar_event_set_guests_can_modify(GDataCalendarEvent * self,gboolean guests_can_modify)1476 gdata_calendar_event_set_guests_can_modify (GDataCalendarEvent *self, gboolean guests_can_modify)
1477 {
1478 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1479 	self->priv->guests_can_modify = guests_can_modify;
1480 	g_object_notify (G_OBJECT (self), "guests-can-modify");
1481 }
1482 
1483 /**
1484  * gdata_calendar_event_get_guests_can_invite_others:
1485  * @self: a #GDataCalendarEvent
1486  *
1487  * Gets the #GDataCalendarEvent:guests-can-invite-others property.
1488  *
1489  * Return value: %TRUE if attendees can invite others to the event, %FALSE otherwise
1490  */
1491 gboolean
gdata_calendar_event_get_guests_can_invite_others(GDataCalendarEvent * self)1492 gdata_calendar_event_get_guests_can_invite_others (GDataCalendarEvent *self)
1493 {
1494 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), FALSE);
1495 	return self->priv->guests_can_invite_others;
1496 }
1497 
1498 /**
1499  * gdata_calendar_event_set_guests_can_invite_others:
1500  * @self: a #GDataCalendarEvent
1501  * @guests_can_invite_others: %TRUE if attendees can invite others to the event, %FALSE otherwise
1502  *
1503  * Sets the #GDataCalendarEvent:guests-can-invite-others property to @guests_can_invite_others.
1504  */
1505 void
gdata_calendar_event_set_guests_can_invite_others(GDataCalendarEvent * self,gboolean guests_can_invite_others)1506 gdata_calendar_event_set_guests_can_invite_others (GDataCalendarEvent *self, gboolean guests_can_invite_others)
1507 {
1508 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1509 	self->priv->guests_can_invite_others = guests_can_invite_others;
1510 	g_object_notify (G_OBJECT (self), "guests-can-invite-others");
1511 }
1512 
1513 /**
1514  * gdata_calendar_event_get_guests_can_see_guests:
1515  * @self: a #GDataCalendarEvent
1516  *
1517  * Gets the #GDataCalendarEvent:guests-can-see-guests property.
1518  *
1519  * Return value: %TRUE if attendees can see who's attending the event, %FALSE otherwise
1520  */
1521 gboolean
gdata_calendar_event_get_guests_can_see_guests(GDataCalendarEvent * self)1522 gdata_calendar_event_get_guests_can_see_guests (GDataCalendarEvent *self)
1523 {
1524 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), FALSE);
1525 	return self->priv->guests_can_see_guests;
1526 }
1527 
1528 /**
1529  * gdata_calendar_event_set_guests_can_see_guests:
1530  * @self: a #GDataCalendarEvent
1531  * @guests_can_see_guests: %TRUE if attendees can see who's attending the event, %FALSE otherwise
1532  *
1533  * Sets the #GDataCalendarEvent:guests-can-see-guests property to @guests_can_see_guests.
1534  */
1535 void
gdata_calendar_event_set_guests_can_see_guests(GDataCalendarEvent * self,gboolean guests_can_see_guests)1536 gdata_calendar_event_set_guests_can_see_guests (GDataCalendarEvent *self, gboolean guests_can_see_guests)
1537 {
1538 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1539 	self->priv->guests_can_see_guests = guests_can_see_guests;
1540 	g_object_notify (G_OBJECT (self), "guests-can-see-guests");
1541 }
1542 
1543 /**
1544  * gdata_calendar_event_get_anyone_can_add_self:
1545  * @self: a #GDataCalendarEvent
1546  *
1547  * Gets the #GDataCalendarEvent:anyone-can-add-self property.
1548  *
1549  * Return value: %TRUE if anyone can add themselves as an attendee to the event, %FALSE otherwise
1550  */
1551 gboolean
gdata_calendar_event_get_anyone_can_add_self(GDataCalendarEvent * self)1552 gdata_calendar_event_get_anyone_can_add_self (GDataCalendarEvent *self)
1553 {
1554 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), FALSE);
1555 	return self->priv->anyone_can_add_self;
1556 }
1557 
1558 /**
1559  * gdata_calendar_event_set_anyone_can_add_self:
1560  * @self: a #GDataCalendarEvent
1561  * @anyone_can_add_self: %TRUE if anyone can add themselves as an attendee to the event, %FALSE otherwise
1562  *
1563  * Sets the #GDataCalendarEvent:anyone-can-add-self property to @anyone_can_add_self.
1564  */
1565 void
gdata_calendar_event_set_anyone_can_add_self(GDataCalendarEvent * self,gboolean anyone_can_add_self)1566 gdata_calendar_event_set_anyone_can_add_self (GDataCalendarEvent *self, gboolean anyone_can_add_self)
1567 {
1568 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1569 	self->priv->anyone_can_add_self = anyone_can_add_self;
1570 	g_object_notify (G_OBJECT (self), "anyone-can-add-self");
1571 }
1572 
1573 /**
1574  * gdata_calendar_event_add_person:
1575  * @self: a #GDataCalendarEvent
1576  * @who: a #GDataGDWho to add
1577  *
1578  * Adds the person @who to the event as a guest (attendee, organiser, performer, etc.), and increments its reference count.
1579  *
1580  * Duplicate people will not be added to the list.
1581  */
1582 void
gdata_calendar_event_add_person(GDataCalendarEvent * self,GDataGDWho * who)1583 gdata_calendar_event_add_person (GDataCalendarEvent *self, GDataGDWho *who)
1584 {
1585 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1586 	g_return_if_fail (GDATA_IS_GD_WHO (who));
1587 
1588 	if (g_list_find_custom (self->priv->people, who, (GCompareFunc) gdata_comparable_compare) == NULL)
1589 		self->priv->people = g_list_append (self->priv->people, g_object_ref (who));
1590 }
1591 
1592 /**
1593  * gdata_calendar_event_get_people:
1594  * @self: a #GDataCalendarEvent
1595  *
1596  * Gets a list of the people attending the event.
1597  *
1598  * Return value: (element-type GData.GDWho) (transfer none): a #GList of #GDataGDWhos, or %NULL
1599  *
1600  * Since: 0.2.0
1601  */
1602 GList *
gdata_calendar_event_get_people(GDataCalendarEvent * self)1603 gdata_calendar_event_get_people (GDataCalendarEvent *self)
1604 {
1605 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), NULL);
1606 	return self->priv->people;
1607 }
1608 
1609 /**
1610  * gdata_calendar_event_add_place:
1611  * @self: a #GDataCalendarEvent
1612  * @where: a #GDataGDWhere to add
1613  *
1614  * Adds the place @where to the event as a location and increments its reference count.
1615  *
1616  * Duplicate places will not be added to the list.
1617  */
1618 void
gdata_calendar_event_add_place(GDataCalendarEvent * self,GDataGDWhere * where)1619 gdata_calendar_event_add_place (GDataCalendarEvent *self, GDataGDWhere *where)
1620 {
1621 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1622 	g_return_if_fail (GDATA_IS_GD_WHERE (where));
1623 
1624 	if (g_list_find_custom (self->priv->places, where, (GCompareFunc) gdata_comparable_compare) == NULL)
1625 		self->priv->places = g_list_append (self->priv->places, g_object_ref (where));
1626 }
1627 
1628 /**
1629  * gdata_calendar_event_get_places:
1630  * @self: a #GDataCalendarEvent
1631  *
1632  * Gets a list of the locations associated with the event.
1633  *
1634  * Return value: (element-type GData.GDWhere) (transfer none): a #GList of #GDataGDWheres, or %NULL
1635  *
1636  * Since: 0.2.0
1637  */
1638 GList *
gdata_calendar_event_get_places(GDataCalendarEvent * self)1639 gdata_calendar_event_get_places (GDataCalendarEvent *self)
1640 {
1641 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), NULL);
1642 	return self->priv->places;
1643 }
1644 
1645 /**
1646  * gdata_calendar_event_add_time:
1647  * @self: a #GDataCalendarEvent
1648  * @when: a #GDataGDWhen to add
1649  *
1650  * Adds @when to the event as a time period when the event happens, and increments its reference count.
1651  *
1652  * Duplicate times will not be added to the list.
1653  *
1654  * Note: gdata_calendar_event_add_time() and gdata_calendar_event_set_recurrence() are mutually
1655  * exclusive, as the server doesn't support positive exceptions to recurrence rules. If recurrences
1656  * are required, use gdata_calendar_event_set_recurrence(). Note that this means reminders cannot
1657  * be set for the event, as they are only supported by #GDataGDWhen. No checks are performed for
1658  * these forbidden conditions, as to do so would break libgdata's API; if both a recurrence is set
1659  * and a specific time is added, the server will return an error when the #GDataCalendarEvent is
1660  * inserted using gdata_service_insert_entry().
1661  *
1662  * Since: 0.2.0
1663  */
1664 void
gdata_calendar_event_add_time(GDataCalendarEvent * self,GDataGDWhen * when)1665 gdata_calendar_event_add_time (GDataCalendarEvent *self, GDataGDWhen *when)
1666 {
1667 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1668 	g_return_if_fail (GDATA_IS_GD_WHEN (when));
1669 
1670 	if (g_list_find_custom (self->priv->times, when, (GCompareFunc) gdata_comparable_compare) == NULL)
1671 		self->priv->times = g_list_append (self->priv->times, g_object_ref (when));
1672 }
1673 
1674 /**
1675  * gdata_calendar_event_get_times:
1676  * @self: a #GDataCalendarEvent
1677  *
1678  * Gets a list of the time periods associated with the event.
1679  *
1680  * Return value: (element-type GData.GDWhen) (transfer none): a #GList of #GDataGDWhens, or %NULL
1681  *
1682  * Since: 0.2.0
1683  */
1684 GList *
gdata_calendar_event_get_times(GDataCalendarEvent * self)1685 gdata_calendar_event_get_times (GDataCalendarEvent *self)
1686 {
1687 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), NULL);
1688 	return self->priv->times;
1689 }
1690 
1691 /**
1692  * gdata_calendar_event_get_primary_time:
1693  * @self: a #GDataCalendarEvent
1694  * @start_time: (out caller-allocates): a #gint64 for the start time, or %NULL
1695  * @end_time: (out caller-allocates): a #gint64 for the end time, or %NULL
1696  * @when: (out callee-allocates) (transfer none): a #GDataGDWhen for the primary time structure, or %NULL
1697  *
1698  * Gets the first time period associated with the event, conveniently returning just its start and
1699  * end times if required.
1700  *
1701  * If there are no time periods, or more than one time period, associated with the event, %FALSE will
1702  * be returned, and the parameters will remain unmodified.
1703  *
1704  * Return value: %TRUE if there is only one time period associated with the event, %FALSE otherwise
1705  *
1706  * Since: 0.2.0
1707  */
1708 gboolean
gdata_calendar_event_get_primary_time(GDataCalendarEvent * self,gint64 * start_time,gint64 * end_time,GDataGDWhen ** when)1709 gdata_calendar_event_get_primary_time (GDataCalendarEvent *self, gint64 *start_time, gint64 *end_time, GDataGDWhen **when)
1710 {
1711 	GDataGDWhen *primary_when;
1712 
1713 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), FALSE);
1714 
1715 	if (self->priv->times == NULL || self->priv->times->next != NULL)
1716 		return FALSE;
1717 
1718 	primary_when = GDATA_GD_WHEN (self->priv->times->data);
1719 	if (start_time != NULL)
1720 		*start_time = gdata_gd_when_get_start_time (primary_when);
1721 	if (end_time != NULL)
1722 		*end_time = gdata_gd_when_get_end_time (primary_when);
1723 	if (when != NULL)
1724 		*when = primary_when;
1725 
1726 	return TRUE;
1727 }
1728 
1729 /**
1730  * gdata_calendar_event_get_recurrence:
1731  * @self: a #GDataCalendarEvent
1732  *
1733  * Gets the #GDataCalendarEvent:recurrence property.
1734  *
1735  * Return value: the event recurrence patterns, or %NULL
1736  *
1737  * Since: 0.3.0
1738  */
1739 const gchar *
gdata_calendar_event_get_recurrence(GDataCalendarEvent * self)1740 gdata_calendar_event_get_recurrence (GDataCalendarEvent *self)
1741 {
1742 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), NULL);
1743 	return self->priv->recurrence;
1744 }
1745 
1746 /**
1747  * gdata_calendar_event_set_recurrence:
1748  * @self: a #GDataCalendarEvent
1749  * @recurrence: (allow-none): a new event recurrence, or %NULL
1750  *
1751  * Sets the #GDataCalendarEvent:recurrence property to the new recurrence, @recurrence.
1752  *
1753  * Set @recurrence to %NULL to unset the property in the event.
1754  *
1755  * Note: gdata_calendar_event_add_time() and gdata_calendar_event_set_recurrence() are mutually
1756  * exclusive. See the documentation for gdata_calendar_event_add_time() for details.
1757  *
1758  * Since: 0.3.0
1759  */
1760 void
gdata_calendar_event_set_recurrence(GDataCalendarEvent * self,const gchar * recurrence)1761 gdata_calendar_event_set_recurrence (GDataCalendarEvent *self, const gchar *recurrence)
1762 {
1763 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1764 
1765 	g_free (self->priv->recurrence);
1766 	self->priv->recurrence = g_strdup (recurrence);
1767 	g_object_notify (G_OBJECT (self), "recurrence");
1768 }
1769 
1770 /**
1771  * gdata_calendar_event_get_original_event_details:
1772  * @self: a #GDataCalendarEvent
1773  * @event_id: (out callee-allocates) (transfer full): return location for the original event's ID, or %NULL
1774  * @event_uri: (out callee-allocates) (transfer full): return location for the original event's URI, or %NULL
1775  *
1776  * Gets details of the original event, if this event is an exception to a recurring event. The original
1777  * event's ID and the URI of the event's XML are returned in @event_id and @event_uri, respectively.
1778  *
1779  * If this event is not an exception to a recurring event, @event_id and @event_uri will be set to %NULL.
1780  * See gdata_calendar_event_is_exception() to determine more simply whether an event is an exception to a
1781  * recurring event.
1782  *
1783  * If both @event_id and @event_uri are %NULL, this function is a no-op. Otherwise, they should both be
1784  * freed with g_free().
1785  *
1786  * Since: 0.3.0
1787  */
1788 void
gdata_calendar_event_get_original_event_details(GDataCalendarEvent * self,gchar ** event_id,gchar ** event_uri)1789 gdata_calendar_event_get_original_event_details (GDataCalendarEvent *self, gchar **event_id, gchar **event_uri)
1790 {
1791 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (self));
1792 
1793 	if (event_id != NULL)
1794 		*event_id = g_strdup (self->priv->original_event_id);
1795 	if (event_uri != NULL)
1796 		*event_uri = g_strdup (self->priv->original_event_uri);
1797 }
1798 
1799 /**
1800  * gdata_calendar_event_is_exception:
1801  * @self: a #GDataCalendarEvent
1802  *
1803  * Determines whether the event is an exception to a recurring event. If it is, details of the original event
1804  * can be retrieved using gdata_calendar_event_get_original_event_details().
1805  *
1806  * Return value: %TRUE if the event is an exception, %FALSE otherwise
1807  *
1808  * Since: 0.3.0
1809  */
1810 gboolean
gdata_calendar_event_is_exception(GDataCalendarEvent * self)1811 gdata_calendar_event_is_exception (GDataCalendarEvent *self)
1812 {
1813 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), FALSE);
1814 	return (self->priv->original_event_id != NULL && self->priv->original_event_uri != NULL) ? TRUE : FALSE;
1815 }
1816