diff options
author | Philip Withnall <philip@tecnocode.co.uk> | 2015-05-04 00:08:50 +0100 |
---|---|---|
committer | Philip Withnall <philip@tecnocode.co.uk> | 2015-05-04 00:47:49 +0100 |
commit | 83d215b9d2bb7e7b1547986c23277b370fce215f (patch) | |
tree | 34fe8050a05c782906238a771aa60ee245a60be2 | |
parent | 69d181787f4ed92690130d50858dc856c462585a (diff) | |
download | libgdata-83d215b9d2bb7e7b1547986c23277b370fce215f.tar.gz |
calendar: Port to the Calendar API version 3
Deprecated API:
• GDataCalendarFeed:timezone, gdata_calendar_feed_get_timezone()
• GDataCalendarFeed:times-cleaned,
gdata_calendar_feed_get_times_cleaned()
• GDataBatchable implementation in GDataCalendarService
• gdata_calendar_service_insert_event(),
gdata_calendar_service_insert_event_async()
• GDataCalendarCalendar:times-cleaned,
gdata_calendar_calendar_get_times_cleaned()
• GDataCalendarCalendar:edited, gdata_calendar_calendar_get_edited()
API changes:
• GDataBatchable implementation in GDataCalendarService will now always
fail
• ACLs are temporarily not supported; support for them will be re-added
in future when they have been ported to the new API
• Add gdata_calendar_service_insert_calendar_event(),
gdata_calendar_service_insert_calendar_event_async()
This includes a partial update of the unit tests, but most of them have
been disabled pending updates.
https://bugzilla.gnome.org/show_bug.cgi?id=664353
-rw-r--r-- | docs/reference/gdata-sections.txt | 2 | ||||
-rw-r--r-- | gdata/gdata.symbols | 2 | ||||
-rw-r--r-- | gdata/services/calendar/gdata-calendar-calendar.c | 311 | ||||
-rw-r--r-- | gdata/services/calendar/gdata-calendar-calendar.h | 9 | ||||
-rw-r--r-- | gdata/services/calendar/gdata-calendar-event.c | 846 | ||||
-rw-r--r-- | gdata/services/calendar/gdata-calendar-feed.c | 91 | ||||
-rw-r--r-- | gdata/services/calendar/gdata-calendar-feed.h | 8 | ||||
-rw-r--r-- | gdata/services/calendar/gdata-calendar-query.c | 3 | ||||
-rw-r--r-- | gdata/services/calendar/gdata-calendar-service.c | 514 | ||||
-rw-r--r-- | gdata/services/calendar/gdata-calendar-service.h | 36 | ||||
-rw-r--r-- | gdata/tests/calendar.c | 696 |
11 files changed, 1775 insertions, 743 deletions
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt index 62d54095..813b4ee2 100644 --- a/docs/reference/gdata-sections.txt +++ b/docs/reference/gdata-sections.txt @@ -470,6 +470,8 @@ gdata_calendar_service_query_events gdata_calendar_service_query_events_async gdata_calendar_service_insert_event gdata_calendar_service_insert_event_async +gdata_calendar_service_insert_calendar_event +gdata_calendar_service_insert_calendar_event_async <SUBSECTION Standard> gdata_calendar_service_get_type GDATA_CALENDAR_SERVICE diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols index 830f0bf3..ffb308bc 100644 --- a/gdata/gdata.symbols +++ b/gdata/gdata.symbols @@ -239,6 +239,7 @@ gdata_calendar_service_query_own_calendars gdata_calendar_service_query_own_calendars_async gdata_calendar_service_query_events gdata_calendar_service_insert_event +gdata_calendar_service_insert_calendar_event gdata_operation_type_get_type gdata_service_error_get_type gdata_media_expression_get_type @@ -857,6 +858,7 @@ gdata_youtube_service_upload_video gdata_youtube_service_finish_video_upload gdata_calendar_service_query_events_async gdata_calendar_service_insert_event_async +gdata_calendar_service_insert_calendar_event_async gdata_contacts_contact_get_photo_async gdata_contacts_contact_get_photo_finish gdata_contacts_contact_set_photo_async diff --git a/gdata/services/calendar/gdata-calendar-calendar.c b/gdata/services/calendar/gdata-calendar-calendar.c index 93f14534..9d7204b3 100644 --- a/gdata/services/calendar/gdata-calendar-calendar.c +++ b/gdata/services/calendar/gdata-calendar-calendar.c @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2009, 2010, 2014, 2015 <philip@tecnocode.co.uk> * * GData Client is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -29,7 +29,7 @@ * access roles defined for the base #GDataAccessRule (e.g. %GDATA_ACCESS_ROLE_NONE), #GDataCalendarCalendar has its own, such as * %GDATA_CALENDAR_ACCESS_ROLE_EDITOR and %GDATA_CALENDAR_ACCESS_ROLE_FREE_BUSY. * - * For more details of Google Calendar's GData API, see the <ulink type="http" url="http://code.google.com/apis/calendar/docs/2.0/reference.html"> + * For more details of Google Calendar's GData API, see the <ulink type="http" url="https://developers.google.com/google-apps/calendar/v3/reference/"> * online documentation</ulink>. * * <example> @@ -77,7 +77,6 @@ #include <config.h> #include <glib.h> #include <glib/gi18n-lib.h> -#include <libxml/parser.h> #include <string.h> #include "gdata-calendar-calendar.h" @@ -89,23 +88,19 @@ #include "gdata-calendar-service.h" static void gdata_calendar_calendar_access_handler_init (GDataAccessHandlerIface *iface); -static GObject *gdata_calendar_calendar_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params); static void gdata_calendar_calendar_finalize (GObject *object); static void gdata_calendar_calendar_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void gdata_calendar_calendar_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); -static void get_xml (GDataParsable *parsable, GString *xml_string); -static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error); -static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces); +static void get_json (GDataParsable *parsable, JsonBuilder *builder); +static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error); +static const gchar *get_content_type (void); struct _GDataCalendarCalendarPrivate { gchar *timezone; - guint times_cleaned; gboolean is_hidden; GDataColor colour; gboolean is_selected; gchar *access_level; - - gint64 edited; }; enum { @@ -131,16 +126,15 @@ gdata_calendar_calendar_class_init (GDataCalendarCalendarClass *klass) g_type_class_add_private (klass, sizeof (GDataCalendarCalendarPrivate)); - gobject_class->constructor = gdata_calendar_calendar_constructor; gobject_class->set_property = gdata_calendar_calendar_set_property; gobject_class->get_property = gdata_calendar_calendar_get_property; gobject_class->finalize = gdata_calendar_calendar_finalize; - parsable_class->parse_xml = parse_xml; - parsable_class->get_xml = get_xml; - parsable_class->get_namespaces = get_namespaces; + parsable_class->parse_json = parse_json; + parsable_class->get_json = get_json; + parsable_class->get_content_type = get_content_type; - entry_class->kind_term = "http://schemas.google.com/gCal/2005#calendarmeta"; + entry_class->kind_term = "calendar#calendarListEntry"; /** * GDataCalendarCalendar:timezone: @@ -158,12 +152,18 @@ gdata_calendar_calendar_class_init (GDataCalendarCalendarClass *klass) * GDataCalendarCalendar:times-cleaned: * * The number of times the calendar has been cleared of events. + * + * Deprecated: UNRELEASED: Unsupported by the online API any more. There + * is no replacement; this will always return + * <code class="literal">0</code>. **/ g_object_class_install_property (gobject_class, PROP_TIMES_CLEANED, g_param_spec_uint ("times-cleaned", "Times cleaned", "The number of times the calendar has been cleared of events.", 0, G_MAXUINT, 0, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + G_PARAM_DEPRECATED | + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); /** * GDataCalendarCalendar:is-hidden: @@ -181,12 +181,13 @@ gdata_calendar_calendar_class_init (GDataCalendarCalendarClass *klass) /** * GDataCalendarCalendar:color: * - * The color used to highlight the calendar in the user's browser. This must be one of a limited set of colors listed in the - * <ulink type="http" url="http://code.google.com/apis/calendar/data/2.0/reference.html#gCalcolor">online documentation</ulink>. + * The background color used to highlight the calendar in the user’s + * browser. This used to be restricted to a limited set of colours, but + * since UNRELEASED may be any RGB colour. **/ g_object_class_install_property (gobject_class, PROP_COLOR, g_param_spec_boxed ("color", - "Color", "The color used to highlight the calendar in the user's browser.", + "Color", "The background color used to highlight the calendar in the user's browser.", GDATA_TYPE_COLOR, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); @@ -223,12 +224,17 @@ gdata_calendar_calendar_class_init (GDataCalendarCalendarClass *klass) * * For more information, see the <ulink type="http" url="http://www.atomenabled.org/developers/protocol/#appEdited"> * Atom Publishing Protocol specification</ulink>. + * + * Deprecated: UNRELEASED: Unsupported by the online API any more. There + * is no replacement; this will always return -1. **/ g_object_class_install_property (gobject_class, PROP_EDITED, g_param_spec_int64 ("edited", "Edited", "The last time the calendar was edited.", -1, G_MAXINT64, -1, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + G_PARAM_DEPRECATED | + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); /* Override the ETag property since ETags don't seem to be supported for calendars. */ g_object_class_override_property (gobject_class, PROP_ETAG, "etag"); @@ -257,28 +263,6 @@ static void gdata_calendar_calendar_init (GDataCalendarCalendar *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_CALENDAR_CALENDAR, GDataCalendarCalendarPrivate); - self->priv->edited = -1; -} - -static GObject * -gdata_calendar_calendar_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params) -{ - GObject *object; - - /* Chain up to the parent class */ - object = G_OBJECT_CLASS (gdata_calendar_calendar_parent_class)->constructor (type, n_construct_params, construct_params); - - if (_gdata_parsable_is_constructed_from_xml (GDATA_PARSABLE (object)) == FALSE) { - GDataCalendarCalendarPrivate *priv = GDATA_CALENDAR_CALENDAR (object)->priv; - GTimeVal time_val; - - /* Set the edited property to the current time (creation time). We don't do this in *_init() since that would cause - * setting it from parse_xml() to fail (duplicate element). */ - g_get_current_time (&time_val); - priv->edited = time_val.tv_sec; - } - - return object; } static void @@ -296,14 +280,17 @@ gdata_calendar_calendar_finalize (GObject *object) static void gdata_calendar_calendar_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { - GDataCalendarCalendarPrivate *priv = GDATA_CALENDAR_CALENDAR (object)->priv; + GDataCalendarCalendar *self = GDATA_CALENDAR_CALENDAR (object); + GDataCalendarCalendarPrivate *priv = self->priv; switch (property_id) { case PROP_TIMEZONE: g_value_set_string (value, priv->timezone); break; case PROP_TIMES_CLEANED: - g_value_set_uint (value, priv->times_cleaned); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + g_value_set_uint (value, gdata_calendar_calendar_get_times_cleaned (self)); + G_GNUC_END_IGNORE_DEPRECATIONS break; case PROP_IS_HIDDEN: g_value_set_boolean (value, priv->is_hidden); @@ -318,7 +305,10 @@ gdata_calendar_calendar_get_property (GObject *object, guint property_id, GValue g_value_set_string (value, priv->access_level); break; case PROP_EDITED: - g_value_set_int64 (value, priv->edited); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + g_value_set_int64 (value, + gdata_calendar_calendar_get_edited (self)); + G_GNUC_END_IGNORE_DEPRECATIONS break; case PROP_ETAG: /* Never return an ETag */ @@ -360,113 +350,176 @@ gdata_calendar_calendar_set_property (GObject *object, guint property_id, const } static gboolean -parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error) +parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error) { gboolean success; GDataCalendarCalendar *self = GDATA_CALENDAR_CALENDAR (parsable); - if (gdata_parser_is_namespace (node, "http://www.w3.org/2007/app") == TRUE && - gdata_parser_int64_time_from_element (node, "edited", P_REQUIRED | P_NO_DUPES, &(self->priv->edited), &success, error) == TRUE) { + /* FIXME: Unimplemented: + * - location + * - summaryOverride + * - colorId + * - foregroundColor + * - defaultReminders + * - notificationSettings + * - primary + * - deleted + */ + + if (gdata_parser_string_from_json_member (reader, "timeZone", P_DEFAULT, &self->priv->timezone, &success, error) || + gdata_parser_color_from_json_member (reader, "backgroundColor", P_DEFAULT, &self->priv->colour, &success, error) || + gdata_parser_boolean_from_json_member (reader, "hidden", P_DEFAULT, &self->priv->is_hidden, &success, error) || + gdata_parser_boolean_from_json_member (reader, "selected", P_DEFAULT, &self->priv->is_selected, &success, error)) { + return success; + } else if (g_strcmp0 (json_reader_get_member_name (reader), "summary") == 0) { + gchar *summary = NULL; + + g_assert (gdata_parser_string_from_json_member (reader, + "summary", + P_DEFAULT, + &summary, + &success, + error)); + + if (summary != NULL) { + gdata_entry_set_title (GDATA_ENTRY (parsable), summary); + } + + g_free (summary); + + return success; + } else if (g_strcmp0 (json_reader_get_member_name (reader), "description") == 0) { + gchar *description = NULL; + + g_assert (gdata_parser_string_from_json_member (reader, + "description", + P_DEFAULT, + &description, + &success, + error)); + + if (description != NULL) { + gdata_entry_set_summary (GDATA_ENTRY (parsable), + description); + } + + g_free (description); + return success; - } else if (gdata_parser_is_namespace (node, "http://schemas.google.com/gCal/2005") == TRUE) { - if (xmlStrcmp (node->name, (xmlChar*) "timezone") == 0) { - /* gCal:timezone */ - xmlChar *_timezone = xmlGetProp (node, (xmlChar*) "value"); - if (_timezone == NULL) - return gdata_parser_error_required_property_missing (node, "value", error); - self->priv->timezone = (gchar*) _timezone; - } else if (xmlStrcmp (node->name, (xmlChar*) "timesCleaned") == 0) { - /* gCal:timesCleaned */ - xmlChar *times_cleaned = xmlGetProp (node, (xmlChar*) "value"); - if (times_cleaned == NULL) - return gdata_parser_error_required_property_missing (node, "value", error); - self->priv->times_cleaned = g_ascii_strtoull ((gchar*) times_cleaned, NULL, 10); - xmlFree (times_cleaned); - } else if (xmlStrcmp (node->name, (xmlChar*) "hidden") == 0) { - /* gCal:hidden */ - if (gdata_parser_boolean_from_property (node, "value", &(self->priv->is_hidden), -1, error) == FALSE) - return FALSE; - } else if (xmlStrcmp (node->name, (xmlChar*) "color") == 0) { - /* gCal:color */ - xmlChar *value; - GDataColor colour; - - value = xmlGetProp (node, (xmlChar*) "value"); - if (value == NULL) - return gdata_parser_error_required_property_missing (node, "value", error); - if (gdata_color_from_hexadecimal ((gchar*) value, &colour) == FALSE) { - /* Error */ - g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, - /* Translators: the first parameter is the name of an XML element (including the angle brackets - * ("<" and ">"), and the second parameter is the erroneous value (which was not in hexadecimal - * RGB format). - * - * For example: - * The content of a <entry/gCal:color> element ("00FG56") was not in hexadecimal RGB format. */ - _("The content of a %s element (\"%s\") was not in hexadecimal RGB format."), - "<entry/gCal:color>", value); - xmlFree (value); - - return FALSE; + } else if (g_strcmp0 (json_reader_get_member_name (reader), "accessRole") == 0) { + gchar *access_role = NULL; + + g_assert (gdata_parser_string_from_json_member (reader, + "accessRole", + P_DEFAULT, + &access_role, + &success, + error)); + + if (access_role != NULL) { + const gchar *level; + + /* Convert from v3 format to v2. */ + if (g_strcmp0 (access_role, "freeBusyReader") == 0) { + level = GDATA_CALENDAR_ACCESS_ROLE_FREE_BUSY; + } else if (g_strcmp0 (access_role, "reader") == 0) { + level = GDATA_CALENDAR_ACCESS_ROLE_READ; + } else if (g_strcmp0 (access_role, "writer") == 0) { + level = GDATA_CALENDAR_ACCESS_ROLE_EDITOR; + } else if (g_strcmp0 (access_role, "owner") == 0) { + level = GDATA_CALENDAR_ACCESS_ROLE_OWNER; + } else { + level = access_role; } - gdata_calendar_calendar_set_color (self, &colour); - xmlFree (value); - } else if (xmlStrcmp (node->name, (xmlChar*) "selected") == 0) { - /* gCal:selected */ - if (gdata_parser_boolean_from_property (node, "value", &(self->priv->is_selected), -1, error) == FALSE) - return FALSE; - } else if (xmlStrcmp (node->name, (xmlChar*) "accesslevel") == 0) { - /* gCal:accesslevel */ - self->priv->access_level = (gchar*) xmlGetProp (node, (xmlChar*) "value"); - if (self->priv->access_level == NULL) - return gdata_parser_error_required_property_missing (node, "value", error); - } else { - return GDATA_PARSABLE_CLASS (gdata_calendar_calendar_parent_class)->parse_xml (parsable, doc, node, user_data, error); + self->priv->access_level = g_strdup (level); + } + + g_free (access_role); + + return success; + } else if (g_strcmp0 (json_reader_get_member_name (reader), "id") == 0) { + GDataLink *_link; + const gchar *id; + gchar *uri; + + /* Calendar entries don’t contain their own selfLink, so we have + * to add one manually. */ + id = json_reader_get_string_value (reader); + if (id != NULL && *id != '\0') { + uri = g_strconcat ("https://www.googleapis.com/calendar/v3/calendars/", id, NULL); + _link = gdata_link_new (uri, GDATA_LINK_SELF); + gdata_entry_add_link (GDATA_ENTRY (parsable), _link); + g_object_unref (_link); + g_free (uri); } + + return GDATA_PARSABLE_CLASS (gdata_calendar_calendar_parent_class)->parse_json (parsable, reader, user_data, error); } else { - return GDATA_PARSABLE_CLASS (gdata_calendar_calendar_parent_class)->parse_xml (parsable, doc, node, user_data, error); + return GDATA_PARSABLE_CLASS (gdata_calendar_calendar_parent_class)->parse_json (parsable, reader, user_data, error); } return TRUE; } static void -get_xml (GDataParsable *parsable, GString *xml_string) +get_json (GDataParsable *parsable, JsonBuilder *builder) { + const gchar *id, *etag, *title, *description; gchar *colour; GDataCalendarCalendarPrivate *priv = GDATA_CALENDAR_CALENDAR (parsable)->priv; - /* Chain up to the parent class */ - GDATA_PARSABLE_CLASS (gdata_calendar_calendar_parent_class)->get_xml (parsable, xml_string); + id = gdata_entry_get_id (GDATA_ENTRY (parsable)); + if (id != NULL) { + json_builder_set_member_name (builder, "id"); + json_builder_add_string_value (builder, id); + } + + json_builder_set_member_name (builder, "kind"); + json_builder_add_string_value (builder, "calendar#calendar"); - /* Add all the Calendar-specific XML */ - if (priv->timezone != NULL) - gdata_parser_string_append_escaped (xml_string, "<gCal:timezone value='", priv->timezone, "'/>"); + /* Add the ETag, if available. */ + etag = gdata_entry_get_etag (GDATA_ENTRY (parsable)); + if (etag != NULL) { + json_builder_set_member_name (builder, "etag"); + json_builder_add_string_value (builder, etag); + } + + /* Calendar labels titles as ‘summary’. */ + title = gdata_entry_get_title (GDATA_ENTRY (parsable)); + if (title != NULL) { + json_builder_set_member_name (builder, "summary"); + json_builder_add_string_value (builder, title); + } + + description = gdata_entry_get_summary (GDATA_ENTRY (parsable)); + if (description != NULL) { + json_builder_set_member_name (builder, "description"); + json_builder_add_string_value (builder, description); + } - if (priv->is_hidden == TRUE) - g_string_append (xml_string, "<gCal:hidden value='true'/>"); - else - g_string_append (xml_string, "<gCal:hidden value='false'/>"); + /* Add all the calendar-specific JSON */ + if (priv->timezone != NULL) { + json_builder_set_member_name (builder, "timeZone"); + json_builder_add_string_value (builder, priv->timezone); + } + + json_builder_set_member_name (builder, "hidden"); + json_builder_add_boolean_value (builder, priv->is_hidden); - colour = gdata_color_to_hexadecimal (&(priv->colour)); - g_string_append_printf (xml_string, "<gCal:color value='%s'/>", colour); + colour = gdata_color_to_hexadecimal (&priv->colour); + json_builder_set_member_name (builder, "backgroundColor"); + json_builder_add_string_value (builder, colour); g_free (colour); - if (priv->is_selected == TRUE) - g_string_append (xml_string, "<gCal:selected value='true'/>"); - else - g_string_append (xml_string, "<gCal:selected value='false'/>"); + json_builder_set_member_name (builder, "selected"); + json_builder_add_boolean_value (builder, priv->is_selected); } -static void -get_namespaces (GDataParsable *parsable, GHashTable *namespaces) +static const gchar * +get_content_type (void) { - /* Chain up to the parent class */ - GDATA_PARSABLE_CLASS (gdata_calendar_calendar_parent_class)->get_namespaces (parsable, namespaces); - - g_hash_table_insert (namespaces, (gchar*) "gCal", (gchar*) "http://schemas.google.com/gCal/2005"); - g_hash_table_insert (namespaces, (gchar*) "app", (gchar*) "http://www.w3.org/2007/app"); + return "application/json"; } /** @@ -525,12 +578,14 @@ gdata_calendar_calendar_set_timezone (GDataCalendarCalendar *self, const gchar * * Gets the #GDataCalendarCalendar:times-cleaned property. * * Return value: the number of times the calendar has been totally emptied + * Deprecated: UNRELEASED: Unsupported by the online API any more. There is no + * replacement; this will always return <code class="literal">0</code>. **/ guint gdata_calendar_calendar_get_times_cleaned (GDataCalendarCalendar *self) { g_return_val_if_fail (GDATA_IS_CALENDAR_CALENDAR (self), 0); - return self->priv->times_cleaned; + return 0; } /** @@ -654,10 +709,12 @@ gdata_calendar_calendar_get_access_level (GDataCalendarCalendar *self) * Gets the #GDataCalendarCalendar:edited property. If the property is unset, <code class="literal">-1</code> will be returned. * * Return value: the UNIX timestamp for the time the calendar was last edited, or <code class="literal">-1</code> + * Deprecated: UNRELEASED: Unsupported by the online API any more. There is no + * replacement; this will always return <code class="literal">-1</code>. **/ gint64 gdata_calendar_calendar_get_edited (GDataCalendarCalendar *self) { g_return_val_if_fail (GDATA_IS_CALENDAR_CALENDAR (self), -1); - return self->priv->edited; + return -1; } diff --git a/gdata/services/calendar/gdata-calendar-calendar.h b/gdata/services/calendar/gdata-calendar-calendar.h index 6bfceb0a..37a368ff 100644 --- a/gdata/services/calendar/gdata-calendar-calendar.h +++ b/gdata/services/calendar/gdata-calendar-calendar.h @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2009, 2015 <philip@tecnocode.co.uk> * * GData Client is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -114,7 +114,6 @@ GDataCalendarCalendar *gdata_calendar_calendar_new (const gchar *id) G_GNUC_WARN const gchar *gdata_calendar_calendar_get_timezone (GDataCalendarCalendar *self) G_GNUC_PURE; void gdata_calendar_calendar_set_timezone (GDataCalendarCalendar *self, const gchar *_timezone); -guint gdata_calendar_calendar_get_times_cleaned (GDataCalendarCalendar *self) G_GNUC_PURE; gboolean gdata_calendar_calendar_is_hidden (GDataCalendarCalendar *self) G_GNUC_PURE; void gdata_calendar_calendar_set_is_hidden (GDataCalendarCalendar *self, gboolean is_hidden); void gdata_calendar_calendar_get_color (GDataCalendarCalendar *self, GDataColor *color); @@ -122,7 +121,11 @@ void gdata_calendar_calendar_set_color (GDataCalendarCalendar *self, const GData gboolean gdata_calendar_calendar_is_selected (GDataCalendarCalendar *self) G_GNUC_PURE; void gdata_calendar_calendar_set_is_selected (GDataCalendarCalendar *self, gboolean is_selected); const gchar *gdata_calendar_calendar_get_access_level (GDataCalendarCalendar *self) G_GNUC_PURE; -gint64 gdata_calendar_calendar_get_edited (GDataCalendarCalendar *self); + +#ifndef LIBGDATA_DISABLE_DEPRECATED +guint gdata_calendar_calendar_get_times_cleaned (GDataCalendarCalendar *self) G_GNUC_PURE G_GNUC_DEPRECATED; +gint64 gdata_calendar_calendar_get_edited (GDataCalendarCalendar *self) G_GNUC_DEPRECATED; +#endif /* !LIBGDATA_DISABLE_DEPRECATED */ G_END_DECLS diff --git a/gdata/services/calendar/gdata-calendar-event.c b/gdata/services/calendar/gdata-calendar-event.c index 069bf338..a6dfdc6a 100644 --- a/gdata/services/calendar/gdata-calendar-event.c +++ b/gdata/services/calendar/gdata-calendar-event.c @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2009, 2010, 2014, 2015 <philip@tecnocode.co.uk> * * GData Client is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -25,7 +25,8 @@ * * #GDataCalendarEvent is a subclass of #GDataEntry to represent an event on a calendar from Google Calendar. * - * For more details of Google Calendar's GData API, see the <ulink type="http" url="http://code.google.com/apis/calendar/docs/2.0/reference.html"> + * For more details of Google Calendar's GData API, see the + * <ulink type="http" url="https://developers.google.com/google-apps/calendar/v3/reference/"> * online documentation</ulink>. * * <example> @@ -84,7 +85,6 @@ #include <config.h> #include <glib.h> #include <glib/gi18n-lib.h> -#include <libxml/parser.h> #include <string.h> #include "gdata-calendar-event.h" @@ -99,9 +99,9 @@ static void gdata_calendar_event_dispose (GObject *object); static void gdata_calendar_event_finalize (GObject *object); static void gdata_calendar_event_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void gdata_calendar_event_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); -static void get_xml (GDataParsable *parsable, GString *xml_string); -static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error); -static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces); +static void get_json (GDataParsable *parsable, JsonBuilder *builder); +static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error); +static const gchar *get_content_type (void); struct _GDataCalendarEventPrivate { gint64 edited; @@ -109,17 +109,27 @@ struct _GDataCalendarEventPrivate { gchar *visibility; gchar *transparency; gchar *uid; - guint sequence; + gint64 sequence; GList *times; /* GDataGDWhen */ - guint guests_can_modify : 1; - guint guests_can_invite_others : 1; - guint guests_can_see_guests : 1; - guint anyone_can_add_self : 1; + gboolean guests_can_modify; + gboolean guests_can_invite_others; + gboolean guests_can_see_guests; + gboolean anyone_can_add_self; GList *people; /* GDataGDWho */ GList *places; /* GDataGDWhere */ gchar *recurrence; gchar *original_event_id; gchar *original_event_uri; + + /* Parsing state. */ + struct { + gint64 start_time; + gint64 end_time; + gboolean seen_start; + gboolean seen_end; + gboolean start_is_date; + gboolean end_is_date; + } parser; }; enum { @@ -155,11 +165,11 @@ gdata_calendar_event_class_init (GDataCalendarEventClass *klass) gobject_class->dispose = gdata_calendar_event_dispose; gobject_class->finalize = gdata_calendar_event_finalize; - parsable_class->parse_xml = parse_xml; - parsable_class->get_xml = get_xml; - parsable_class->get_namespaces = get_namespaces; + parsable_class->parse_json = parse_json; + parsable_class->get_json = get_json; + parsable_class->get_content_type = get_content_type; - entry_class->kind_term = "http://schemas.google.com/g/2005#event"; + entry_class->kind_term = "calendar#event"; /** * GDataCalendarEvent:edited: @@ -250,7 +260,8 @@ gdata_calendar_event_class_init (GDataCalendarEventClass *klass) * Indicates whether attendees may modify the original event, so that changes are visible to organizers and other attendees. * Otherwise, any changes made by attendees will be restricted to that attendee's calendar. * - * For more information, see the <ulink type="http" url="http://code.google.com/apis/calendar/docs/2.0/reference.html#gCalguestsCanModify"> + * For more information, see the + * <ulink type="http" url="https://developers.google.com/google-apps/calendar/v3/reference/events#guestsCanInviteOthers"> * GData specification</ulink>. **/ g_object_class_install_property (gobject_class, PROP_GUESTS_CAN_MODIFY, @@ -265,7 +276,7 @@ gdata_calendar_event_class_init (GDataCalendarEventClass *klass) * Indicates whether attendees may invite others to the event. * * For more information, see the <ulink type="http" - * url="http://code.google.com/apis/calendar/docs/2.0/reference.html#gCalguestsCanInviteOthers">GData specification</ulink>. + * url="https://developers.google.com/google-apps/calendar/v3/reference/events#guestsCanInviteOthers">GData specification</ulink>. **/ g_object_class_install_property (gobject_class, PROP_GUESTS_CAN_INVITE_OTHERS, g_param_spec_boolean ("guests-can-invite-others", @@ -278,7 +289,8 @@ gdata_calendar_event_class_init (GDataCalendarEventClass *klass) * * Indicates whether attendees can see other people invited to the event. * - * For more information, see the <ulink type="http" url="http://code.google.com/apis/calendar/docs/2.0/reference.html#gCalguestsCanSeeGuests"> + * For more information, see the + * <ulink type="http" url="https://developers.google.com/google-apps/calendar/v3/reference/events#guestsCanSeeOtherGuests"> * GData specification</ulink>. **/ g_object_class_install_property (gobject_class, PROP_GUESTS_CAN_SEE_GUESTS, @@ -438,7 +450,7 @@ gdata_calendar_event_get_property (GObject *object, guint property_id, GValue *v g_value_set_string (value, priv->uid); break; case PROP_SEQUENCE: - g_value_set_uint (value, priv->sequence); + g_value_set_uint (value, CLAMP (priv->sequence, 0, G_MAXUINT)); break; case PROP_GUESTS_CAN_MODIFY: g_value_set_boolean (value, priv->guests_can_modify); @@ -512,182 +524,698 @@ gdata_calendar_event_set_property (GObject *object, guint property_id, const GVa } static gboolean -parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error) +date_object_from_json (JsonReader *reader, + const gchar *member_name, + GDataParserOptions options, + gint64 *date_time_output, + gboolean *is_date_output, + gboolean *success, + GError **error) +{ + gint64 date_time; + gboolean is_date = FALSE; + gboolean found_member = FALSE; + + /* Check if there’s such an element */ + if (g_strcmp0 (json_reader_get_member_name (reader), member_name) != 0) { + return FALSE; + } + + /* Check that it’s an object. */ + if (!json_reader_is_object (reader)) { + const GError *child_error; + + /* Manufacture an error. */ + json_reader_read_member (reader, "dateTime"); + child_error = json_reader_get_error (reader); + g_assert (child_error != NULL); + *success = gdata_parser_error_from_json_error (reader, + child_error, + error); + json_reader_end_member (reader); + + return TRUE; + } + + /* Try to parse either the dateTime or date member. */ + if (json_reader_read_member (reader, "dateTime")) { + const gchar *date_string; + const GError *child_error; + GTimeVal time_val; + + date_string = json_reader_get_string_value (reader); + child_error = json_reader_get_error (reader); + + if (child_error != NULL) { + *success = gdata_parser_error_from_json_error (reader, + child_error, + error); + json_reader_end_member (reader); + return TRUE; + } + + if (!g_time_val_from_iso8601 (date_string, &time_val)) { + *success = gdata_parser_error_not_iso8601_format_json (reader, date_string, error); + json_reader_end_member (reader); + return TRUE; + } + + date_time = time_val.tv_sec; + is_date = FALSE; + found_member = TRUE; + } + json_reader_end_member (reader); + + if (json_reader_read_member (reader, "date")) { + const gchar *date_string; + const GError *child_error; + + date_string = json_reader_get_string_value (reader); + child_error = json_reader_get_error (reader); + + if (child_error != NULL) { + *success = gdata_parser_error_from_json_error (reader, + child_error, + error); + json_reader_end_member (reader); + return TRUE; + } + + if (!gdata_parser_int64_from_date (date_string, &date_time)) { + *success = gdata_parser_error_not_iso8601_format_json (reader, date_string, error); + json_reader_end_member (reader); + return TRUE; + } + + is_date = TRUE; + found_member = TRUE; + } + json_reader_end_member (reader); + + /* Ignore timeZone; it should be specified in dateTime. */ + if (!found_member) { + *success = gdata_parser_error_required_json_content_missing (reader, error); + return TRUE; + } + + *date_time_output = date_time; + *is_date_output = is_date; + *success = TRUE; + + return TRUE; +} + +/* Convert between v2 and v3 versions of various enum values. v2 uses a URI + * style with a constant prefix; v3 simply drops this prefix, and changes the + * spelling of ‘canceled’ to ‘cancelled’. */ +#define V2_PREFIX "http://schemas.google.com/g/2005#event." + +static gchar * +add_v2_prefix (const gchar *in) +{ + return g_strconcat (V2_PREFIX, in, NULL); +} + +static const gchar * +strip_v2_prefix (const gchar *uri) +{ + /* Convert to v3 format. */ + if (g_str_has_prefix (uri, V2_PREFIX)) { + return uri + strlen (V2_PREFIX); + } else { + return uri; + } +} + +static gboolean +parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error) { gboolean success; GDataCalendarEvent *self = GDATA_CALENDAR_EVENT (parsable); + GDataCalendarEventPrivate *priv = self->priv; + + /* FIXME: Currently unsupported: + * - htmlLink + * - colorId + * - endTimeUnspecified + * - originalStartTime + * - attendeesOmitted + * - extendedProperties + * - hangoutLink + * - gadget + * - privateCopy + * - locked + * - reminders + * - source + */ + + if (g_strcmp0 (json_reader_get_member_name (reader), "start") == 0) { + self->priv->parser.seen_start = TRUE; + } else if (g_strcmp0 (json_reader_get_member_name (reader), "end") == 0) { + self->priv->parser.seen_end = TRUE; + } + + if (gdata_parser_string_from_json_member (reader, "recurringEventId", P_DEFAULT, &self->priv->original_event_id, &success, error) || + gdata_parser_boolean_from_json_member (reader, "guestsCanModify", P_DEFAULT, &self->priv->guests_can_modify, &success, error) || + gdata_parser_boolean_from_json_member (reader, "guestsCanInviteOthers", P_DEFAULT, &self->priv->guests_can_invite_others, &success, error) || + gdata_parser_boolean_from_json_member (reader, "guestsCanSeeOtherGuests", P_DEFAULT, &self->priv->guests_can_see_guests, &success, error) || + gdata_parser_boolean_from_json_member (reader, "anyoneCanAddSelf", P_DEFAULT, &self->priv->anyone_can_add_self, &success, error) || + gdata_parser_string_from_json_member (reader, "iCalUID", P_DEFAULT, &self->priv->uid, &success, error) || + gdata_parser_int_from_json_member (reader, "sequence", P_DEFAULT, &self->priv->sequence, &success, error) || + gdata_parser_int64_time_from_json_member (reader, "updated", P_DEFAULT, &self->priv->edited, &success, error) || + date_object_from_json (reader, "start", P_DEFAULT, &self->priv->parser.start_time, &self->priv->parser.start_is_date, &success, error) || + date_object_from_json (reader, "end", P_DEFAULT, &self->priv->parser.end_time, &self->priv->parser.end_is_date, &success, error)) { + if (success) { + if (self->priv->edited != -1) { + _gdata_entry_set_updated (GDATA_ENTRY (parsable), + self->priv->edited); + } + + if (self->priv->original_event_id != NULL) { + g_free (self->priv->original_event_uri); + self->priv->original_event_uri = g_strconcat ("https://www.googleapis.com/calendar/v3/events/", + self->priv->original_event_id, NULL); + } + + if (self->priv->parser.seen_start && self->priv->parser.seen_end) { + GDataGDWhen *when; + + when = gdata_gd_when_new (self->priv->parser.start_time, + self->priv->parser.end_time, + self->priv->parser.start_is_date || + self->priv->parser.end_is_date); + self->priv->times = g_list_prepend (self->priv->times, when); /* transfer ownership */ + + self->priv->parser.seen_start = FALSE; + self->priv->parser.seen_end = FALSE; + } + } - if (gdata_parser_is_namespace (node, "http://www.w3.org/2007/app") == TRUE && - gdata_parser_int64_time_from_element (node, "edited", P_REQUIRED | P_NO_DUPES, &(self->priv->edited), &success, error) == TRUE) { return success; - } else if (gdata_parser_is_namespace (node, "http://schemas.google.com/g/2005") == TRUE) { - if (gdata_parser_object_from_element_setter (node, "when", P_REQUIRED, GDATA_TYPE_GD_WHEN, - gdata_calendar_event_add_time, self, &success, error) == TRUE || - gdata_parser_object_from_element_setter (node, "who", P_REQUIRED, GDATA_TYPE_GD_WHO, - gdata_calendar_event_add_person, self, &success, error) == TRUE || - gdata_parser_object_from_element_setter (node, "where", P_REQUIRED, GDATA_TYPE_GD_WHERE, - gdata_calendar_event_add_place, self, &success, error) == TRUE) { - return success; - } else if (xmlStrcmp (node->name, (xmlChar*) "eventStatus") == 0) { - /* gd:eventStatus */ - xmlChar *value = xmlGetProp (node, (xmlChar*) "value"); - if (value == NULL) - return gdata_parser_error_required_property_missing (node, "value", error); - self->priv->status = (gchar*) value; - } else if (xmlStrcmp (node->name, (xmlChar*) "visibility") == 0) { - /* gd:visibility */ - xmlChar *value = xmlGetProp (node, (xmlChar*) "value"); - if (value == NULL) - return gdata_parser_error_required_property_missing (node, "value", error); - self->priv->visibility = (gchar*) value; - } else if (xmlStrcmp (node->name, (xmlChar*) "transparency") == 0) { - /* gd:transparency */ - xmlChar *value = xmlGetProp (node, (xmlChar*) "value"); - if (value == NULL) - return gdata_parser_error_required_property_missing (node, "value", error); - self->priv->transparency = (gchar*) value; - } else if (xmlStrcmp (node->name, (xmlChar*) "recurrence") == 0) { - /* gd:recurrence */ - self->priv->recurrence = (gchar*) xmlNodeListGetString (doc, node->children, TRUE); - } else if (xmlStrcmp (node->name, (xmlChar*) "originalEvent") == 0) { - /* gd:originalEvent */ - self->priv->original_event_id = (gchar*) xmlGetProp (node, (xmlChar*) "id"); - self->priv->original_event_uri = (gchar*) xmlGetProp (node, (xmlChar*) "href"); - } else { - return GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->parse_xml (parsable, doc, node, user_data, error); + } else if (g_strcmp0 (json_reader_get_member_name (reader), "transparency") == 0) { + gchar *transparency = NULL; /* owned */ + + g_assert (gdata_parser_string_from_json_member (reader, + "transparency", + P_DEFAULT, + &transparency, + &success, + error)); + + if (success) { + priv->transparency = add_v2_prefix (transparency); } - } else if (gdata_parser_is_namespace (node, "http://schemas.google.com/gCal/2005") == TRUE) { - if (xmlStrcmp (node->name, (xmlChar*) "uid") == 0) { - /* gCal:uid */ - xmlChar *value = xmlGetProp (node, (xmlChar*) "value"); - if (value == NULL) - return gdata_parser_error_required_property_missing (node, "value", error); - self->priv->uid = (gchar*) value; - } else if (xmlStrcmp (node->name, (xmlChar*) "sequence") == 0) { - /* gCal:sequence */ - xmlChar *value; - guint value_uint; - - value = xmlGetProp (node, (xmlChar*) "value"); - if (value == NULL) - return gdata_parser_error_required_property_missing (node, "value", error); - else - value_uint = g_ascii_strtoull ((gchar*) value, NULL, 10); - xmlFree (value); - - gdata_calendar_event_set_sequence (self, value_uint); - } else if (xmlStrcmp (node->name, (xmlChar*) "guestsCanModify") == 0) { - /* gCal:guestsCanModify */ - gboolean guests_can_modify; - if (gdata_parser_boolean_from_property (node, "value", &guests_can_modify, -1, error) == FALSE) - return FALSE; - gdata_calendar_event_set_guests_can_modify (self, guests_can_modify); - } else if (xmlStrcmp (node->name, (xmlChar*) "guestsCanInviteOthers") == 0) { - /* gCal:guestsCanInviteOthers */ - gboolean guests_can_invite_others; - if (gdata_parser_boolean_from_property (node, "value", &guests_can_invite_others, -1, error) == FALSE) - return FALSE; - gdata_calendar_event_set_guests_can_invite_others (self, guests_can_invite_others); - } else if (xmlStrcmp (node->name, (xmlChar*) "guestsCanSeeGuests") == 0) { - /* gCal:guestsCanSeeGuests */ - gboolean guests_can_see_guests; - if (gdata_parser_boolean_from_property (node, "value", &guests_can_see_guests, -1, error) == FALSE) + + g_free (transparency); + + return success; + } else if (g_strcmp0 (json_reader_get_member_name (reader), "visibility") == 0) { + gchar *visibility = NULL; /* owned */ + + g_assert (gdata_parser_string_from_json_member (reader, + "visibility", + P_DEFAULT, + &visibility, + &success, + error)); + + if (success) { + priv->visibility = add_v2_prefix (visibility); + } + + g_free (visibility); + + return success; + } else if (g_strcmp0 (json_reader_get_member_name (reader), "status") == 0) { + gchar *status = NULL; /* owned */ + + g_assert (gdata_parser_string_from_json_member (reader, + "status", + P_DEFAULT, + &status, + &success, + error)); + + if (success) { + if (g_strcmp0 (status, "cancelled") == 0) { + /* Those damned British Englishes. */ + priv->status = add_v2_prefix ("canceled"); + } else { + priv->status = add_v2_prefix (status); + } + } + + g_free (status); + + return success; + } else if (g_strcmp0 (json_reader_get_member_name (reader), "summary") == 0) { + const gchar *summary; + const GError *child_error = NULL; + + summary = json_reader_get_string_value (reader); + child_error = json_reader_get_error (reader); + + if (child_error != NULL) { + gdata_parser_error_from_json_error (reader, + child_error, error); + return FALSE; + } + + gdata_entry_set_title (GDATA_ENTRY (parsable), summary); + } else if (g_strcmp0 (json_reader_get_member_name (reader), "description") == 0) { + const gchar *description; + const GError *child_error = NULL; + + description = json_reader_get_string_value (reader); + child_error = json_reader_get_error (reader); + + if (child_error != NULL) { + gdata_parser_error_from_json_error (reader, + child_error, error); + return FALSE; + } + + gdata_entry_set_content (GDATA_ENTRY (parsable), description); + } else if (g_strcmp0 (json_reader_get_member_name (reader), "location") == 0) { + const gchar *location; + GDataGDWhere *where = NULL; /* owned */ + const GError *child_error = NULL; + + location = json_reader_get_string_value (reader); + child_error = json_reader_get_error (reader); + + if (child_error != NULL) { + gdata_parser_error_from_json_error (reader, + child_error, error); + return FALSE; + } + + where = gdata_gd_where_new (GDATA_GD_WHERE_EVENT, + location, NULL); + priv->places = g_list_prepend (priv->places, where); /* transfer ownership */ + } else if (g_strcmp0 (json_reader_get_member_name (reader), "created") == 0) { + gint64 created; + + g_assert (gdata_parser_int64_time_from_json_member (reader, + "created", + P_DEFAULT, + &created, + &success, + error)); + + if (success) { + _gdata_entry_set_published (GDATA_ENTRY (parsable), + created); + } + + return success; + } else if (g_strcmp0 (json_reader_get_member_name (reader), "recurrence") == 0) { + guint i, j; + GString *recurrence = NULL; /* owned */ + + /* In the JSON API, the recurrence is given as an array of + * strings, each giving an RFC 2445 property such as RRULE, + * EXRULE, RDATE or EXDATE. Concatenate them all to form a + * recurrence string as used in v2 of the API. */ + if (self->priv->recurrence != NULL) { + return gdata_parser_error_duplicate_json_element (reader, + error); + } + + recurrence = g_string_new (""); + + for (i = 0, j = json_reader_count_elements (reader); i < j; i++) { + const gchar *line; + const GError *child_error; + + json_reader_read_element (reader, i); + + line = json_reader_get_string_value (reader); + child_error = json_reader_get_error (reader); + if (child_error != NULL) { + gdata_parser_error_from_json_error (reader, child_error, error); + json_reader_end_element (reader); return FALSE; - gdata_calendar_event_set_guests_can_see_guests (self, guests_can_see_guests); - } else if (xmlStrcmp (node->name, (xmlChar*) "anyoneCanAddSelf") == 0) { - /* gCal:anyoneCanAddSelf */ - gboolean anyone_can_add_self; - if (gdata_parser_boolean_from_property (node, "value", &anyone_can_add_self, -1, error) == FALSE) + } + + g_string_append (recurrence, line); + g_string_append (recurrence, "\n"); + + json_reader_end_element (reader); + } + + g_assert (self->priv->recurrence == NULL); + self->priv->recurrence = g_string_free (recurrence, FALSE); + + return TRUE; + } else if (g_strcmp0 (json_reader_get_member_name (reader), "attendees") == 0) { + guint i, j; + + if (priv->people != NULL) { + return gdata_parser_error_duplicate_json_element (reader, + error); + } + + for (i = 0, j = json_reader_count_elements (reader); i < j; i++) { + GDataGDWho *who = NULL; /* owned */ + const gchar *email_address, *value_string; + const gchar *relation_type; + gboolean is_organizer, is_resource; + const GError *child_error; + + json_reader_read_element (reader, i); + + json_reader_read_member (reader, "responseStatus"); + child_error = json_reader_get_error (reader); + if (child_error != NULL) { + gdata_parser_error_from_json_error (reader, + child_error, + error); + json_reader_end_member (reader); return FALSE; - gdata_calendar_event_set_anyone_can_add_self (self, anyone_can_add_self); - } else { - return GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->parse_xml (parsable, doc, node, user_data, error); + } + json_reader_end_member (reader); + + json_reader_read_member (reader, "email"); + email_address = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "displayName"); + value_string = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "organizer"); + is_organizer = json_reader_get_boolean_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "resource"); + is_resource = json_reader_get_boolean_value (reader); + json_reader_end_member (reader); + + /* FIXME: Currently unsupported: + * - id + * - self + * - optional (writeble) + * - responseStatus (writeble) + * - comment (writeble) + * - additionalGuests (writeble) + */ + + if (is_organizer) { + relation_type = GDATA_GD_WHO_EVENT_ORGANIZER; + } else if (!is_resource) { + relation_type = GDATA_GD_WHO_EVENT_ATTENDEE; + } else { + /* FIXME: Add support for resources. */ + relation_type = NULL; + } + + who = gdata_gd_who_new (relation_type, value_string, + email_address); + priv->people = g_list_prepend (priv->people, who); /* transfer ownership */ + + json_reader_end_element (reader); } + } else if (g_strcmp0 (json_reader_get_member_name (reader), "creator") == 0 || + g_strcmp0 (json_reader_get_member_name (reader), "organizer") == 0) { + /* These are read-only and already handled as part of + * ‘attendees’, so ignore them. */ + return TRUE; } else { - return GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->parse_xml (parsable, doc, node, user_data, error); + return GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->parse_json (parsable, reader, user_data, error); } return TRUE; } static void -get_child_xml (GList *list, GString *xml_string) +get_json (GDataParsable *parsable, JsonBuilder *builder) { - GList *i; + GList *l; + const gchar *id, *etag, *title, *description; + GDataGDWho *organiser_who = NULL; /* unowned */ + GDataCalendarEventPrivate *priv = GDATA_CALENDAR_EVENT (parsable)->priv; - for (i = list; i != NULL; i = i->next) - _gdata_parsable_get_xml (GDATA_PARSABLE (i->data), xml_string, FALSE); -} + /* FIXME: Support: + * - colorId + * - attendeesOmitted + * - extendedProperties + * - gadget + * - reminders + * - source + */ -static void -get_xml (GDataParsable *parsable, GString *xml_string) -{ - GDataCalendarEventPrivate *priv = GDATA_CALENDAR_EVENT (parsable)->priv; + id = gdata_entry_get_id (GDATA_ENTRY (parsable)); + if (id != NULL) { + json_builder_set_member_name (builder, "id"); + json_builder_add_string_value (builder, id); + } - /* Chain up to the parent class */ - GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->get_xml (parsable, xml_string); + json_builder_set_member_name (builder, "kind"); + json_builder_add_string_value (builder, "calendar#event"); - /* Add all the Calendar-specific XML */ + /* Add the ETag, if available. */ + etag = gdata_entry_get_etag (GDATA_ENTRY (parsable)); + if (etag != NULL) { + json_builder_set_member_name (builder, "etag"); + json_builder_add_string_value (builder, etag); + } - /* TODO: gd:comments? */ + /* Calendar labels titles as ‘summary’. */ + title = gdata_entry_get_title (GDATA_ENTRY (parsable)); + if (title != NULL) { + json_builder_set_member_name (builder, "summary"); + json_builder_add_string_value (builder, title); + } - if (priv->status != NULL) - gdata_parser_string_append_escaped (xml_string, "<gd:eventStatus value='", priv->status, "'/>"); + description = gdata_entry_get_content (GDATA_ENTRY (parsable)); + if (description != NULL) { + json_builder_set_member_name (builder, "description"); + json_builder_add_string_value (builder, description); + } - if (priv->visibility != NULL) - gdata_parser_string_append_escaped (xml_string, "<gd:visibility value='", priv->visibility, "'/>"); + /* Add all the calendar-specific JSON */ + json_builder_set_member_name (builder, "anyoneCanAddSelf"); + json_builder_add_boolean_value (builder, priv->anyone_can_add_self); - if (priv->transparency != NULL) - gdata_parser_string_append_escaped (xml_string, "<gd:transparency value='", priv->transparency, "'/>"); + json_builder_set_member_name (builder, "guestsCanInviteOthers"); + json_builder_add_boolean_value (builder, priv->guests_can_invite_others); - if (priv->uid != NULL) - gdata_parser_string_append_escaped (xml_string, "<gCal:uid value='", priv->uid, "'/>"); + json_builder_set_member_name (builder, "guestsCanModify"); + json_builder_add_boolean_value (builder, priv->guests_can_modify); - if (priv->sequence != 0) - g_string_append_printf (xml_string, "<gCal:sequence value='%u'/>", priv->sequence); + json_builder_set_member_name (builder, "guestsCanSeeOtherGuests"); + json_builder_add_boolean_value (builder, priv->guests_can_see_guests); - if (priv->guests_can_modify == TRUE) - g_string_append (xml_string, "<gCal:guestsCanModify value='true'/>"); - else - g_string_append (xml_string, "<gCal:guestsCanModify value='false'/>"); + if (priv->transparency != NULL) { + json_builder_set_member_name (builder, "transparency"); + json_builder_add_string_value (builder, + strip_v2_prefix (priv->transparency)); + } - if (priv->guests_can_invite_others == TRUE) - g_string_append (xml_string, "<gCal:guestsCanInviteOthers value='true'/>"); - else - g_string_append (xml_string, "<gCal:guestsCanInviteOthers value='false'/>"); + if (priv->visibility != NULL) { + json_builder_set_member_name (builder, "visibility"); + json_builder_add_string_value (builder, + strip_v2_prefix (priv->visibility)); + } - if (priv->guests_can_see_guests == TRUE) - g_string_append (xml_string, "<gCal:guestsCanSeeGuests value='true'/>"); - else - g_string_append (xml_string, "<gCal:guestsCanSeeGuests value='false'/>"); + if (priv->uid != NULL) { + json_builder_set_member_name (builder, "iCalUID"); + json_builder_add_string_value (builder, priv->uid); + } - if (priv->anyone_can_add_self == TRUE) - g_string_append (xml_string, "<gCal:anyoneCanAddSelf value='true'/>"); - else - g_string_append (xml_string, "<gCal:anyoneCanAddSelf value='false'/>"); + if (priv->sequence > 0) { + json_builder_set_member_name (builder, "sequence"); + json_builder_add_int_value (builder, priv->sequence); + } - if (priv->recurrence != NULL) - gdata_parser_string_append_escaped (xml_string, "<gd:recurrence>", priv->recurrence, "</gd:recurrence>"); + if (priv->status != NULL) { + const gchar *status; - get_child_xml (priv->times, xml_string); - get_child_xml (priv->people, xml_string); - get_child_xml (priv->places, xml_string); + /* Convert to v3 format. */ + status = strip_v2_prefix (priv->status); + if (g_strcmp0 (status, "canceled") == 0) { + status = "cancelled"; + } - /* TODO: - * - Finish supporting all tags - * - Check all tags here are valid for insertions and updates - */ + json_builder_set_member_name (builder, "status"); + json_builder_add_string_value (builder, status); + } + + if (priv->recurrence != NULL) { + gchar **parts; + guint i; + + json_builder_set_member_name (builder, "recurrence"); + json_builder_begin_array (builder); + + parts = g_strsplit (priv->recurrence, "\n", -1); + + for (i = 0; parts[i] != NULL; i++) { + json_builder_add_string_value (builder, parts[i]); + } + + g_strfreev (parts); + + json_builder_end_array (builder); + } + + if (priv->original_event_id != NULL) { + json_builder_set_member_name (builder, "recurringEventId"); + json_builder_add_string_value (builder, priv->original_event_id); + } + + /* Times. */ + for (l = priv->times; l != NULL; l = l->next) { + GDataGDWhen *when; /* unowned */ + gchar *val = NULL; /* owned */ + const gchar *member_name; + gint64 start_time, end_time; + + when = l->data; + + /* Start time. */ + start_time = gdata_gd_when_get_start_time (when); + json_builder_set_member_name (builder, "start"); + json_builder_begin_object (builder); + + if (gdata_gd_when_is_date (when)) { + member_name = "date"; + val = gdata_parser_date_from_int64 (start_time); + } else { + member_name = "dateTime"; + val = gdata_parser_int64_to_iso8601 (start_time); + } + + json_builder_set_member_name (builder, member_name); + json_builder_add_string_value (builder, val); + g_free (val); + + json_builder_set_member_name (builder, "timeZone"); + json_builder_add_string_value (builder, "UTC"); + + json_builder_end_object (builder); + + /* End time. */ + end_time = gdata_gd_when_get_end_time (when); + + if (end_time > -1) { + json_builder_set_member_name (builder, "end"); + json_builder_begin_object (builder); + + if (gdata_gd_when_is_date (when)) { + member_name = "date"; + val = gdata_parser_date_from_int64 (end_time); + } else { + member_name = "dateTime"; + val = gdata_parser_int64_to_iso8601 (end_time); + } + + json_builder_set_member_name (builder, member_name); + json_builder_add_string_value (builder, val); + g_free (val); + + json_builder_set_member_name (builder, "timeZone"); + json_builder_add_string_value (builder, "UTC"); + + json_builder_end_object (builder); + } else { + json_builder_set_member_name (builder, "endTimeUnspecified"); + json_builder_add_boolean_value (builder, TRUE); + } + + /* Only use the first time. :-( + * FIXME: There must be a better solution. */ + if (l->next != NULL) { + g_warning ("Ignoring secondary times; they are no " + "longer supported by the server-side API."); + break; + } + } + + /* Locations. */ + for (l = priv->places; l != NULL; l = l->next) { + GDataGDWhere *where; /* unowned */ + const gchar *location; + + where = l->data; + location = gdata_gd_where_get_value_string (where); + + json_builder_set_member_name (builder, "location"); + json_builder_add_string_value (builder, location); + + /* Only use the first location. :-( + * FIXME: There must be a better solution. */ + if (l->next != NULL) { + g_warning ("Ignoring secondary locations; they are no " + "longer supported by the server-side API."); + break; + } + } + + /* People. */ + json_builder_set_member_name (builder, "attendees"); + json_builder_begin_array (builder); + + for (l = priv->people; l != NULL; l = l->next) { + GDataGDWho *who; /* unowned */ + const gchar *display_name, *email_address; + + who = l->data; + + json_builder_begin_object (builder); + + display_name = gdata_gd_who_get_value_string (who); + if (display_name != NULL) { + json_builder_set_member_name (builder, "displayName"); + json_builder_add_string_value (builder, display_name); + } + + email_address = gdata_gd_who_get_email_address (who); + if (email_address != NULL) { + json_builder_set_member_name (builder, "email"); + json_builder_add_string_value (builder, email_address); + } + + if (g_strcmp0 (gdata_gd_who_get_relation_type (who), + GDATA_GD_WHO_EVENT_ORGANIZER) == 0) { + json_builder_set_member_name (builder, "organizer"); + json_builder_add_boolean_value (builder, TRUE); + + organiser_who = who; + } + + json_builder_end_object (builder); + } + + json_builder_end_array (builder); + + if (organiser_who != NULL) { + const gchar *display_name, *email_address; + + json_builder_set_member_name (builder, "organizer"); + json_builder_begin_object (builder); + + display_name = gdata_gd_who_get_value_string (organiser_who); + if (display_name != NULL) { + json_builder_set_member_name (builder, "displayName"); + json_builder_add_string_value (builder, display_name); + } + + email_address = gdata_gd_who_get_email_address (organiser_who); + if (email_address != NULL) { + json_builder_set_member_name (builder, "email"); + json_builder_add_string_value (builder, email_address); + } + + json_builder_end_object (builder); + } } -static void -get_namespaces (GDataParsable *parsable, GHashTable *namespaces) +static const gchar * +get_content_type (void) { - /* Chain up to the parent class */ - GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->get_namespaces (parsable, namespaces); - - g_hash_table_insert (namespaces, (gchar*) "gd", (gchar*) "http://schemas.google.com/g/2005"); - g_hash_table_insert (namespaces, (gchar*) "gCal", (gchar*) "http://schemas.google.com/gCal/2005"); - g_hash_table_insert (namespaces, (gchar*) "app", (gchar*) "http://www.w3.org/2007/app"); + return "application/json"; } /** @@ -872,7 +1400,7 @@ guint gdata_calendar_event_get_sequence (GDataCalendarEvent *self) { g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), 0); - return self->priv->sequence; + return CLAMP (self->priv->sequence, 0, G_MAXUINT); } /** diff --git a/gdata/services/calendar/gdata-calendar-feed.c b/gdata/services/calendar/gdata-calendar-feed.c index ce0ca915..b1bf4ff3 100644 --- a/gdata/services/calendar/gdata-calendar-feed.c +++ b/gdata/services/calendar/gdata-calendar-feed.c @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2009, 2010, 2014, 2015 <philip@tecnocode.co.uk> * * GData Client is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -34,14 +34,7 @@ #include "gdata-feed.h" #include "gdata-private.h" -static void gdata_calendar_feed_finalize (GObject *object); static void gdata_calendar_feed_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); -static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error); - -struct _GDataCalendarFeedPrivate { - gchar *timezone; - guint times_cleaned; -}; enum { PROP_TIMEZONE = 1, @@ -54,14 +47,8 @@ static void gdata_calendar_feed_class_init (GDataCalendarFeedClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass); - - g_type_class_add_private (klass, sizeof (GDataCalendarFeedPrivate)); gobject_class->get_property = gdata_calendar_feed_get_property; - gobject_class->finalize = gdata_calendar_feed_finalize; - - parsable_class->parse_xml = parse_xml; /** * GDataCalendarFeed:timezone: @@ -70,12 +57,16 @@ gdata_calendar_feed_class_init (GDataCalendarFeedClass *klass) * url="http://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones">reference</ulink>. * * Since: 0.3.0 + * Deprecated: UNRELEASED: Unsupported by the online API any more. There + * is no replacement; this will always return %NULL. **/ g_object_class_install_property (gobject_class, PROP_TIMEZONE, g_param_spec_string ("timezone", "Timezone", "The timezone in which the feed's times are given.", NULL, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + G_PARAM_DEPRECATED | + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); /** * GDataCalendarFeed:times-cleaned: @@ -83,42 +74,41 @@ gdata_calendar_feed_class_init (GDataCalendarFeedClass *klass) * The number of times the feed has been completely cleared of entries. * * Since: 0.3.0 + * Deprecated: UNRELEASED: Unsupported by the online API any more. There + * is no replacement; this will always return 0. **/ g_object_class_install_property (gobject_class, PROP_TIMES_CLEANED, g_param_spec_uint ("times-cleaned", "Times cleaned", "The number of times the feed has been completely cleared of entries.", 0, G_MAXUINT, 0, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + G_PARAM_DEPRECATED | + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); } static void gdata_calendar_feed_init (GDataCalendarFeed *self) { - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_CALENDAR_FEED, GDataCalendarFeedPrivate); -} - -static void -gdata_calendar_feed_finalize (GObject *object) -{ - GDataCalendarFeedPrivate *priv = GDATA_CALENDAR_FEED (object)->priv; - - g_free (priv->timezone); - - /* Chain up to the parent class */ - G_OBJECT_CLASS (gdata_calendar_feed_parent_class)->finalize (object); + /* Nothing to see here. */ } static void gdata_calendar_feed_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { - GDataCalendarFeedPrivate *priv = GDATA_CALENDAR_FEED (object)->priv; + GDataCalendarFeed *self = GDATA_CALENDAR_FEED (object); switch (property_id) { case PROP_TIMEZONE: - g_value_set_string (value, priv->timezone); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + g_value_set_string (value, + gdata_calendar_feed_get_timezone (self)); + G_GNUC_END_IGNORE_DEPRECATIONS break; case PROP_TIMES_CLEANED: - g_value_set_uint (value, priv->times_cleaned); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + g_value_set_uint (value, + gdata_calendar_feed_get_times_cleaned (self)); + G_GNUC_END_IGNORE_DEPRECATIONS break; default: /* We don't have any other property... */ @@ -127,35 +117,6 @@ gdata_calendar_feed_get_property (GObject *object, guint property_id, GValue *va } } -static gboolean -parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error) -{ - GDataCalendarFeed *self = GDATA_CALENDAR_FEED (parsable); - - if (gdata_parser_is_namespace (node, "http://schemas.google.com/gCal/2005") == FALSE) - return GDATA_PARSABLE_CLASS (gdata_calendar_feed_parent_class)->parse_xml (parsable, doc, node, user_data, error); - - if (xmlStrcmp (node->name, (xmlChar*) "timezone") == 0) { - /* gCal:timezone */ - xmlChar *_timezone = xmlGetProp (node, (xmlChar*) "value"); - if (_timezone == NULL) - return gdata_parser_error_required_property_missing (node, "value", error); - g_free (self->priv->timezone); - self->priv->timezone = (gchar*) _timezone; - } else if (xmlStrcmp (node->name, (xmlChar*) "timesCleaned") == 0) { - /* gCal:timesCleaned */ - xmlChar *times_cleaned = xmlGetProp (node, (xmlChar*) "value"); - if (times_cleaned == NULL) - return gdata_parser_error_required_property_missing (node, "value", error); - self->priv->times_cleaned = g_ascii_strtoull ((gchar*) times_cleaned, NULL, 10); - xmlFree (times_cleaned); - } else { - return GDATA_PARSABLE_CLASS (gdata_calendar_feed_parent_class)->parse_xml (parsable, doc, node, user_data, error); - } - - return TRUE; -} - /** * gdata_calendar_feed_get_timezone: * @self: a #GDataCalendarFeed @@ -165,12 +126,15 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da * Return value: the feed's timezone, or %NULL * * Since: 0.3.0 + * Deprecated: UNRELEASED: Unsupported by the online API any more. There is no + * replacement; this will always return %NULL. **/ const gchar * gdata_calendar_feed_get_timezone (GDataCalendarFeed *self) { + /* Not supported any more by version 3 of the API. */ g_return_val_if_fail (GDATA_IS_CALENDAR_FEED (self), NULL); - return self->priv->timezone; + return NULL; } /** @@ -182,10 +146,13 @@ gdata_calendar_feed_get_timezone (GDataCalendarFeed *self) * Return value: the number of times the feed has been totally emptied * * Since: 0.3.0 + * Deprecated: UNRELEASED: Unsupported by the online API any more. There is no + * replacement; this will always return %NULL. **/ guint gdata_calendar_feed_get_times_cleaned (GDataCalendarFeed *self) { + /* Not supported any more by version 3 of the API. */ g_return_val_if_fail (GDATA_IS_CALENDAR_FEED (self), 0); - return self->priv->times_cleaned; + return 0; } diff --git a/gdata/services/calendar/gdata-calendar-feed.h b/gdata/services/calendar/gdata-calendar-feed.h index 0cf062af..f9b2bc15 100644 --- a/gdata/services/calendar/gdata-calendar-feed.h +++ b/gdata/services/calendar/gdata-calendar-feed.h @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2009, 2015 <philip@tecnocode.co.uk> * * GData Client is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -68,8 +68,10 @@ typedef struct { GType gdata_calendar_feed_get_type (void); -const gchar *gdata_calendar_feed_get_timezone (GDataCalendarFeed *self) G_GNUC_PURE; -guint gdata_calendar_feed_get_times_cleaned (GDataCalendarFeed *self) G_GNUC_PURE; +#ifndef LIBGDATA_DISABLE_DEPRECATED +const gchar *gdata_calendar_feed_get_timezone (GDataCalendarFeed *self) G_GNUC_PURE G_GNUC_DEPRECATED; +guint gdata_calendar_feed_get_times_cleaned (GDataCalendarFeed *self) G_GNUC_PURE G_GNUC_DEPRECATED; +#endif /* !LIBGDATA_DISABLE_DEPRECATED */ G_END_DECLS diff --git a/gdata/services/calendar/gdata-calendar-query.c b/gdata/services/calendar/gdata-calendar-query.c index 85da9171..8198b897 100644 --- a/gdata/services/calendar/gdata-calendar-query.c +++ b/gdata/services/calendar/gdata-calendar-query.c @@ -27,7 +27,8 @@ * those catered for by #GDataQuery. * * For more information on the custom GData query parameters supported by #GDataCalendarQuery, see the <ulink type="http" - * url="http://code.google.com/apis/calendar/docs/2.0/reference.html#Parameters">online documentation</ulink>. + * url="https://developers.google.com/google-apps/calendar/v3/reference/events/list">online + * documentation</ulink>. * * <example> * <title>Querying for Events</title> diff --git a/gdata/services/calendar/gdata-calendar-service.c b/gdata/services/calendar/gdata-calendar-service.c index e326eabd..924fc5be 100644 --- a/gdata/services/calendar/gdata-calendar-service.c +++ b/gdata/services/calendar/gdata-calendar-service.c @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2009, 2014, 2015 <philip@tecnocode.co.uk> * * GData Client is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -26,12 +26,13 @@ * #GDataCalendarService is a subclass of #GDataService for communicating with the GData API of Google Calendar. It supports querying * for, inserting, editing and deleting events from calendars, as well as operations on the calendars themselves. * - * For more details of Google Calendar's GData API, see the <ulink type="http" url="http://code.google.com/apis/calendar/docs/2.0/reference.html"> + * For more details of Google Calendar's GData API, see the + * <ulink type="http" url="https://developers.google.com/google-apps/calendar/v3/reference/"> * online documentation</ulink>. * * 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 * which users the calendar is shared with. For more information about ACLs for calendars, see the - * <ulink type="http" url="http://code.google.com/apis/calendar/data/2.0/developers_guide_protocol.html#SharingACalendar">online documentation on + * <ulink type="http" url="https://developers.google.com/google-apps/calendar/v3/reference/acl">online documentation on * sharing calendars</ulink>. * * <example> @@ -128,76 +129,9 @@ * </programlisting> * </example> * - * The Calendar service can be manipulated using batch operations, too. See the - * <ulink type="http" url="http://code.google.com/apis/calendar/data/2.0/developers_guide_protocol.html#Batch">online documentation on batch - * operations</ulink> for more information. - * - * <example> - * <title>Performing a Batch Operation on a Calendar</title> - * <programlisting> - * GDataCalendarService *service; - * GDataCalendarCalendar *calendar; - * GDataBatchOperation *operation; - * GDataLink *batch_link; - * GList *i; - * GError *error = NULL; - * - * /<!-- -->* Create a service and retrieve a calendar to work on *<!-- -->/ - * service = create_calendar_service (); - * calendar = get_calendar (service); - * - * /<!-- -->* Create the batch operation *<!-- -->/ - * batch_link = gdata_entry_look_up_link (GDATA_ENTRY (calendar), GDATA_LINK_BATCH); - * operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_link_get_uri (batch_link)); - * - * g_object_unref (calendar); - * - * gdata_batch_operation_add_query (operation, event_entry_id_to_query, GDATA_TYPE_CALENDAR_EVENT, - * (GDataBatchOperationCallback) batch_query_cb, user_data); - * gdata_batch_operation_add_insertion (operation, new_entry, (GDataBatchOperationCallback) batch_insertion_cb, user_data); - * gdata_batch_operation_add_update (operation, old_entry, (GDataBatchOperationCallback) batch_update_cb, user_data); - * gdata_batch_operation_add_deletion (operation, entry_to_delete, (GDataBatchOperationCallback) batch_deletion_cb, user_data); - * - * /<!-- -->* Run the batch operation and handle the results in the various callbacks *<!-- -->/ - * gdata_test_batch_operation_run (operation, NULL, &error); - * - * g_object_unref (operation); - * g_object_unref (service); - * - * if (error != NULL) { - * g_error ("Error running batch operation: %s", error->message); - * g_error_free (error); - * return; - * } - * - * static void - * batch_query_cb (guint operation_id, GDataBatchOperationType operation_type, GDataEntry *entry, GError *error, gpointer user_data) - * { - * /<!-- -->* operation_type == GDATA_BATCH_OPERATION_QUERY *<!-- -->/ - * /<!-- -->* Reference and do something with the returned entry. *<!-- -->/ - * } - * - * static void - * batch_insertion_cb (guint operation_id, GDataBatchOperationType operation_type, GDataEntry *entry, GError *error, gpointer user_data) - * { - * /<!-- -->* operation_type == GDATA_BATCH_OPERATION_INSERTION *<!-- -->/ - * /<!-- -->* Reference and do something with the returned entry. *<!-- -->/ - * } - * - * static void - * batch_update_cb (guint operation_id, GDataBatchOperationType operation_type, GDataEntry *entry, GError *error, gpointer user_data) - * { - * /<!-- -->* operation_type == GDATA_BATCH_OPERATION_UPDATE *<!-- -->/ - * /<!-- -->* Reference and do something with the returned entry. *<!-- -->/ - * } - * - * static void - * batch_deletion_cb (guint operation_id, GDataBatchOperationType operation_type, GDataEntry *entry, GError *error, gpointer user_data) - * { - * /<!-- -->* operation_type == GDATA_BATCH_OPERATION_DELETION, entry == NULL *<!-- -->/ - * } - * </programlisting> - * </example> + * Before version UNRELEASED, the Calendar service could be manipulated using + * batch operations. That is no longer supported, and any batch operations + * created on the calendar will fail. **/ #include <config.h> @@ -213,8 +147,17 @@ #include "gdata-query.h" #include "gdata-calendar-feed.h" -/* Standards reference here: http://code.google.com/apis/calendar/docs/2.0/reference.html */ +/* Standards reference here: + * https://developers.google.com/google-apps/calendar/v3/reference/ */ +static void +parse_error_response (GDataService *self, + GDataOperationType operation_type, + guint status, + const gchar *reason_phrase, + const gchar *response_body, + gint length, + GError **error); static GList *get_authorization_domains (void); _GDATA_DEFINE_AUTHORIZATION_DOMAIN (calendar, "cl", "https://www.google.com/calendar/feeds/") @@ -225,6 +168,7 @@ gdata_calendar_service_class_init (GDataCalendarServiceClass *klass) { GDataServiceClass *service_class = GDATA_SERVICE_CLASS (klass); service_class->feed_type = GDATA_TYPE_CALENDAR_FEED; + service_class->parse_error_response = parse_error_response; service_class->get_authorization_domains = get_authorization_domains; } @@ -234,6 +178,212 @@ gdata_calendar_service_init (GDataCalendarService *self) /* Nothing to see here */ } +/* The error format used by the Google Calendar API doesn’t seem to be + * documented anywhere, which is a little frustrating. Here’s an example of it: + * { + * "error": { + * "errors": [ + * { + * "domain": "global", + * "reason": "parseError", + * "message": "Parse Error", + * } + * ], + * "code": 400, + * "message": "Parse Error" + * } + * } + * or: + * { + * "error": { + * "errors": [ + * { + * "domain": "global", + * "reason": "required", + * "message": "Missing end time." + * } + * ], + * "code": 400, + * "message": "Missing end time." + * } + * } + */ +static void +parse_error_response (GDataService *self, + GDataOperationType operation_type, + guint status, + const gchar *reason_phrase, + const gchar *response_body, + gint length, + GError **error) +{ + JsonParser *parser = NULL; /* owned */ + JsonReader *reader = NULL; /* owned */ + gint i; + GError *child_error = NULL; + + if (response_body == NULL) { + goto parent; + } + + if (length == -1) { + length = strlen (response_body); + } + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, response_body, length, + &child_error)) { + goto parent; + } + + reader = json_reader_new (json_parser_get_root (parser)); + + /* Check that the outermost node is an object. */ + if (!json_reader_is_object (reader)) { + goto parent; + } + + /* Grab the ‘error’ member, then its ‘errors’ member. */ + if (!json_reader_read_member (reader, "error") || + !json_reader_is_object (reader) || + !json_reader_read_member (reader, "errors") || + !json_reader_is_array (reader)) { + goto parent; + } + + /* Parse each of the errors. Return the first one, and print out any + * others. */ + for (i = 0; i < json_reader_count_elements (reader); i++) { + const gchar *domain, *reason, *message, *extended_help; + const gchar *location_type, *location; + + /* Parse the error. */ + if (!json_reader_read_element (reader, i) || + !json_reader_is_object (reader)) { + goto parent; + } + + json_reader_read_member (reader, "domain"); + domain = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "reason"); + reason = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "message"); + message = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "extendedHelp"); + extended_help = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "locationType"); + location_type = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + json_reader_read_member (reader, "location"); + location = json_reader_get_string_value (reader); + json_reader_end_member (reader); + + /* End the error element. */ + json_reader_end_element (reader); + + /* Create an error message, but only for the first error */ + if (error == NULL || *error == NULL) { + if (g_strcmp0 (domain, "usageLimits") == 0 && + g_strcmp0 (reason, + "dailyLimitExceededUnreg") == 0) { + /* Daily Limit for Unauthenticated Use + * Exceeded. */ + g_set_error (error, GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_API_QUOTA_EXCEEDED, + _("You have made too many API " + "calls recently. Please wait a " + "few minutes and try again.")); + } else if (g_strcmp0 (domain, "global") == 0 && + g_strcmp0 (reason, "notFound") == 0) { + /* Calendar not found. */ + g_set_error (error, GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_NOT_FOUND, + /* Translators: the parameter is an + * error message returned by the + * server. */ + _("The requested resource was not found: %s"), + message); + } else if ((g_strcmp0 (domain, "global") == 0 && + g_strcmp0 (reason, "required") == 0) || + (g_strcmp0 (domain, "global") == 0 && + g_strcmp0 (reason, "conditionNotMet") == 0)) { + /* Client-side protocol error. */ + g_set_error (error, GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_PROTOCOL_ERROR, + /* Translators: the parameter is an + * error message returned by the + * server. */ + _("Invalid request URI or header, " + "or unsupported nonstandard " + "parameter: %s"), message); + } else if (g_strcmp0 (domain, "global") == 0 && + (g_strcmp0 (reason, "authError") == 0 || + g_strcmp0 (reason, "required") == 0)) { + /* Authentication problem */ + g_set_error (error, GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, + _("You must be authenticated to " + "do this.")); + } else if (g_strcmp0 (domain, "global") == 0 && + g_strcmp0 (reason, "forbidden") == 0) { + g_set_error (error, GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_FORBIDDEN, + _("Access was denied by the user " + "or server.")); + } else { + /* Unknown or validation (protocol) error. Fall + * back to working off the HTTP status code. */ + g_warning ("Unknown error code ‘%s’ in domain " + "‘%s’ received with location type " + "‘%s’, location ‘%s’, extended help " + "‘%s’ and message ‘%s’.", + reason, domain, location_type, + location, extended_help, message); + + goto parent; + } + } else { + /* For all errors after the first, log the error in the + * terminal. */ + g_debug ("Error message received in response: domain " + "‘%s’, reason ‘%s’, extended help ‘%s’, " + "message ‘%s’, location type ‘%s’, location " + "‘%s’.", + domain, reason, extended_help, message, + location_type, location); + } + } + + /* End the ‘errors’ and ‘error’ members. */ + json_reader_end_element (reader); + json_reader_end_element (reader); + + g_clear_object (&reader); + g_clear_object (&parser); + + /* Ensure we’ve actually set an error message. */ + g_assert (error == NULL || *error != NULL); + + return; + +parent: + g_clear_object (&reader); + g_clear_object (&parser); + + /* Chain up to the parent class */ + GDATA_SERVICE_CLASS (gdata_calendar_service_parent_class)->parse_error_response (self, operation_type, status, reason_phrase, + response_body, length, error); +} + static GList * get_authorization_domains (void) { @@ -316,7 +466,7 @@ gdata_calendar_service_query_all_calendars (GDataCalendarService *self, GDataQue return NULL; } - request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/allcalendars/full", NULL); + request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.googleapis.com/calendar/v3/users/me/calendarList", NULL); feed = gdata_service_query (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR, cancellable, progress_callback, progress_user_data, error); g_free (request_uri); @@ -370,7 +520,7 @@ gdata_calendar_service_query_all_calendars_async (GDataCalendarService *self, GD return; } - request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/allcalendars/full", NULL); + request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.googleapis.com/calendar/v3/users/me/calendarList", NULL); gdata_service_query_async (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR, cancellable, progress_callback, progress_user_data, destroy_progress_user_data, callback, user_data); g_free (request_uri); @@ -413,7 +563,7 @@ gdata_calendar_service_query_own_calendars (GDataCalendarService *self, GDataQue return NULL; } - request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/owncalendars/full", NULL); + request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.googleapis.com/calendar/v3/users/me/calendarList?minAccessRole=owner", NULL); feed = gdata_service_query (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR, cancellable, progress_callback, progress_user_data, error); g_free (request_uri); @@ -467,12 +617,62 @@ gdata_calendar_service_query_own_calendars_async (GDataCalendarService *self, GD return; } - request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/owncalendars/full", NULL); + request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.googleapis.com/calendar/v3/users/me/calendarList?minAccessRole=owner", NULL); gdata_service_query_async (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR, cancellable, progress_callback, progress_user_data, destroy_progress_user_data, callback, user_data); g_free (request_uri); } +static gchar * +build_events_uri (GDataCalendarCalendar *calendar) +{ + GString *uri; + const gchar *calendar_id; + + calendar_id = (calendar != NULL) ? gdata_entry_get_id (GDATA_ENTRY (calendar)) : "default"; + + uri = g_string_new (_gdata_service_get_scheme ()); + g_string_append (uri, "://www.googleapis.com/calendar/v3/calendars/"); + g_string_append_uri_escaped (uri, calendar_id, NULL, FALSE); + g_string_append (uri, "/events"); + + return g_string_free (uri, FALSE); +} + +static void +set_event_id (GDataCalendarEvent *event, + GDataCalendarCalendar *calendar) +{ + GDataLink *_link = NULL; /* owned */ + const gchar *id, *calendar_id; + gchar *uri = NULL; /* owned */ + + id = gdata_entry_get_id (GDATA_ENTRY (event)); + calendar_id = gdata_entry_get_id (GDATA_ENTRY (calendar)); + + uri = g_strconcat ("https://www.googleapis.com/calendar/v3/calendars/", + calendar_id, "/events/", id, NULL); + _link = gdata_link_new (uri, GDATA_LINK_SELF); + gdata_entry_add_link (GDATA_ENTRY (event), _link); + g_object_unref (_link); + g_free (uri); +} + +static void +set_event_ids (GDataFeed *feed, + GDataCalendarCalendar *calendar) +{ + GList/*<unowned GDataEntry>*/ *entries, *i; /* unowned */ + + /* Add a selfLink to each event, since they don’t contain one by + * default in the data returned by the server. */ + entries = gdata_feed_get_entries (feed); + + for (i = entries; i != NULL; i = i->next) { + set_event_id (GDATA_CALENDAR_EVENT (i->data), calendar); + } +} + /** * gdata_calendar_service_query_events: * @self: a #GDataCalendarService @@ -493,7 +693,8 @@ GDataFeed * gdata_calendar_service_query_events (GDataCalendarService *self, GDataCalendarCalendar *calendar, GDataQuery *query, GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) { - const gchar *uri; + gchar *request_uri; + GDataFeed *feed; g_return_val_if_fail (GDATA_IS_CALENDAR_SERVICE (self), NULL); g_return_val_if_fail (GDATA_IS_CALENDAR_CALENDAR (calendar), NULL); @@ -509,18 +710,20 @@ gdata_calendar_service_query_events (GDataCalendarService *self, GDataCalendarCa return NULL; } - /* Use the calendar's content src */ - uri = gdata_entry_get_content_uri (GDATA_ENTRY (calendar)); - if (uri == NULL) { - /* Erroring out is probably the safest thing to do */ - g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, - _("The calendar did not have a content URI.")); - return NULL; - } + /* Execute the query. */ + request_uri = build_events_uri (calendar); + feed = gdata_service_query (GDATA_SERVICE (self), + get_calendar_authorization_domain (), + request_uri, query, + GDATA_TYPE_CALENDAR_EVENT, cancellable, + progress_callback, progress_user_data, + error); + g_free (request_uri); + + /* Set the events’ IDs. */ + set_event_ids (feed, calendar); - /* Execute the query */ - return gdata_service_query (GDATA_SERVICE (self), get_calendar_authorization_domain (), uri, query, GDATA_TYPE_CALENDAR_EVENT, cancellable, - progress_callback, progress_user_data, error); + return feed; } /** @@ -552,7 +755,7 @@ gdata_calendar_service_query_events_async (GDataCalendarService *self, GDataCale GDestroyNotify destroy_progress_user_data, GAsyncReadyCallback callback, gpointer user_data) { - const gchar *uri; + gchar *request_uri; g_return_if_fail (GDATA_IS_CALENDAR_SERVICE (self)); g_return_if_fail (GDATA_IS_CALENDAR_CALENDAR (calendar)); @@ -572,22 +775,16 @@ gdata_calendar_service_query_events_async (GDataCalendarService *self, GDataCale return; } - /* Use the calendar's content src */ - uri = gdata_entry_get_content_uri (GDATA_ENTRY (calendar)); - if (uri == NULL) { - /* Erroring out is probably the safest thing to do */ - GSimpleAsyncResult *result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_service_query_async); - g_simple_async_result_set_error (result, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, "%s", - _("The calendar did not have a content URI.")); - g_simple_async_result_complete_in_idle (result); - g_object_unref (result); - - return; - } - - /* Execute the query */ - gdata_service_query_async (GDATA_SERVICE (self), get_calendar_authorization_domain (), uri, query, GDATA_TYPE_CALENDAR_EVENT, cancellable, - progress_callback, progress_user_data, destroy_progress_user_data, callback, user_data); + /* Execute the query. */ + request_uri = build_events_uri (calendar); + gdata_service_query_async (GDATA_SERVICE (self), + get_calendar_authorization_domain (), + request_uri, query, + GDATA_TYPE_CALENDAR_EVENT, cancellable, + progress_callback, progress_user_data, + destroy_progress_user_data, callback, + user_data); + g_free (request_uri); } /** @@ -604,11 +801,13 @@ gdata_calendar_service_query_events_async (GDataCalendarService *self, GDataCale * Return value: (transfer full): an updated #GDataCalendarEvent, or %NULL; unref with g_object_unref() * * Since: 0.2.0 + * Deprecated: UNRELEASED: Use gdata_calendar_service_insert_calendar_event() + * instead to be able to specify the calendar to add the event to; otherwise + * the default calendar will be used. **/ GDataCalendarEvent * gdata_calendar_service_insert_event (GDataCalendarService *self, GDataCalendarEvent *event, GCancellable *cancellable, GError **error) { - /* TODO: How do we choose which calendar? */ gchar *uri; GDataEntry *entry; @@ -617,7 +816,7 @@ gdata_calendar_service_insert_event (GDataCalendarService *self, GDataCalendarEv g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/private/full", NULL); + uri = build_events_uri (NULL); entry = gdata_service_insert_entry (GDATA_SERVICE (self), get_calendar_authorization_domain (), uri, GDATA_ENTRY (event), cancellable, error); g_free (uri); @@ -625,6 +824,52 @@ gdata_calendar_service_insert_event (GDataCalendarService *self, GDataCalendarEv } /** + * gdata_calendar_service_insert_calendar_event: + * @self: a #GDataCalendarService + * @calendar: the #GDataCalendarCalendar to insert the event into + * @event: the #GDataCalendarEvent to insert + * @cancellable: (allow-none): optional #GCancellable object, or %NULL + * @error: a #GError, or %NULL + * + * Inserts @event by uploading it to the online calendar service, adding it to + * the specified @calendar. + * + * For more details, see gdata_service_insert_entry(). + * + * Return value: (transfer full): an updated #GDataCalendarEvent, or %NULL; + * unref with g_object_unref() + * + * Since: UNRELEASED + */ +GDataCalendarEvent * +gdata_calendar_service_insert_calendar_event (GDataCalendarService *self, + GDataCalendarCalendar *calendar, + GDataCalendarEvent *event, + GCancellable *cancellable, + GError **error) +{ + gchar *uri; + GDataEntry *entry; + + g_return_val_if_fail (GDATA_IS_CALENDAR_SERVICE (self), NULL); + g_return_val_if_fail (GDATA_IS_CALENDAR_CALENDAR (calendar), NULL); + g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (event), NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + uri = build_events_uri (calendar); + entry = gdata_service_insert_entry (GDATA_SERVICE (self), + get_calendar_authorization_domain (), + uri, GDATA_ENTRY (event), + cancellable, error); + g_free (uri); + + set_event_id (GDATA_CALENDAR_EVENT (entry), calendar); + + return GDATA_CALENDAR_EVENT (entry); +} + +/** * gdata_calendar_service_insert_event_async: * @self: a #GDataCalendarService * @event: the #GDataCalendarEvent to insert @@ -642,6 +887,10 @@ gdata_calendar_service_insert_event (GDataCalendarService *self, GDataCalendarEv * gdata_service_insert_entry_async(), which is the base asynchronous insertion function. * * Since: 0.8.0 + * Deprecated: UNRELEASED: Use + * gdata_calendar_service_insert_calendar_event_async() instead to be able to + * specify the calendar to add the event to; otherwise the default calendar + * will be used. **/ void gdata_calendar_service_insert_event_async (GDataCalendarService *self, GDataCalendarEvent *event, GCancellable *cancellable, @@ -653,8 +902,53 @@ gdata_calendar_service_insert_event_async (GDataCalendarService *self, GDataCale g_return_if_fail (GDATA_IS_CALENDAR_EVENT (event)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); - uri = g_strconcat (_gdata_service_get_scheme (), "://www.google.com/calendar/feeds/default/private/full", NULL); + uri = build_events_uri (NULL); gdata_service_insert_entry_async (GDATA_SERVICE (self), get_calendar_authorization_domain (), uri, GDATA_ENTRY (event), cancellable, callback, user_data); g_free (uri); } + +/** + * gdata_calendar_service_insert_calendar_event_async: + * @self: a #GDataCalendarService + * @calendar: the #GDataCalendarCalendar to insert the event into + * @event: the #GDataCalendarEvent to insert + * @cancellable: (allow-none): optional #GCancellable object, or %NULL + * @callback: a #GAsyncReadyCallback to call when insertion is finished + * @user_data: (closure): data to pass to the @callback function + * + * Inserts @event by uploading it to the online calendar service, adding it to + * the specified @calendar. @self and @event are both reffed when this function + * is called, so can safely be unreffed after this function returns. + * + * @callback should call gdata_service_insert_entry_finish() to obtain a + * #GDataCalendarEvent representing the inserted event and to check for possible + * errors. + * + * For more details, see gdata_calendar_service_insert_event(), which is the + * synchronous version of this function, and gdata_service_insert_entry_async(), + * which is the base asynchronous insertion function. + * + * Since: UNRELEASED + */ +void +gdata_calendar_service_insert_calendar_event_async (GDataCalendarService *self, + GDataCalendarCalendar *calendar, + GDataCalendarEvent *event, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + gchar *uri; + + g_return_if_fail (GDATA_IS_CALENDAR_SERVICE (self)); + g_return_if_fail (GDATA_IS_CALENDAR_EVENT (event)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + uri = build_events_uri (calendar); + gdata_service_insert_entry_async (GDATA_SERVICE (self), + get_calendar_authorization_domain (), + uri, GDATA_ENTRY (event), cancellable, + callback, user_data); + g_free (uri); +} diff --git a/gdata/services/calendar/gdata-calendar-service.h b/gdata/services/calendar/gdata-calendar-service.h index 8177491f..47f245d9 100644 --- a/gdata/services/calendar/gdata-calendar-service.h +++ b/gdata/services/calendar/gdata-calendar-service.h @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2009, 2015 <philip@tecnocode.co.uk> * * GData Client is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -95,10 +95,36 @@ void gdata_calendar_service_query_events_async (GDataCalendarService *self, GDat #include <gdata/services/calendar/gdata-calendar-event.h> -GDataCalendarEvent *gdata_calendar_service_insert_event (GDataCalendarService *self, GDataCalendarEvent *event, - GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; -void gdata_calendar_service_insert_event_async (GDataCalendarService *self, GDataCalendarEvent *event, GCancellable *cancellable, - GAsyncReadyCallback callback, gpointer user_data); +#ifndef LIBGDATA_DISABLE_DEPRECATED +GDataCalendarEvent * +gdata_calendar_service_insert_event (GDataCalendarService *self, + GDataCalendarEvent *event, + GCancellable *cancellable, + GError **error) + G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC + G_GNUC_DEPRECATED_FOR (gdata_calendar_service_insert_calendar_event); +void +gdata_calendar_service_insert_event_async (GDataCalendarService *self, + GDataCalendarEvent *event, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) + G_GNUC_DEPRECATED_FOR (gdata_calendar_service_insert_calendar_event_async); +#endif /* !LIBGDATA_DISABLE_DEPRECATED */ + +GDataCalendarEvent * +gdata_calendar_service_insert_calendar_event (GDataCalendarService *self, + GDataCalendarCalendar *calendar, + GDataCalendarEvent *event, + GCancellable *cancellable, + GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; +void +gdata_calendar_service_insert_calendar_event_async (GDataCalendarService *self, + GDataCalendarCalendar *calendar, + GDataCalendarEvent *event, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); G_END_DECLS diff --git a/gdata/tests/calendar.c b/gdata/tests/calendar.c index c54ddda0..9a30eacd 100644 --- a/gdata/tests/calendar.c +++ b/gdata/tests/calendar.c @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client - * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk> + * Copyright (C) Philip Withnall 2009, 2010, 2014, 2015 <philip@tecnocode.co.uk> * * GData Client is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -23,9 +23,16 @@ #include "gdata.h" #include "common.h" +#include "gdata-dummy-authorizer.h" static UhmServer *mock_server = NULL; +#undef CLIENT_ID /* from common.h */ + +#define CLIENT_ID "352818697630-nqu2cmt5quqd6lr17ouoqmb684u84l1f.apps.googleusercontent.com" +#define CLIENT_SECRET "-fA4pHQJxR3zJ-FyAMPQsikg" +#define REDIRECT_URI "urn:ietf:wg:oauth:2.0:oob" + typedef struct { GDataCalendarCalendar *calendar; } TempCalendarData; @@ -46,7 +53,7 @@ set_up_temp_calendar (TempCalendarData *data, gconstpointer service) gdata_calendar_calendar_set_color (calendar, &colour); data->calendar = GDATA_CALENDAR_CALENDAR (gdata_service_insert_entry (GDATA_SERVICE (service), gdata_calendar_service_get_primary_authorization_domain (), - "https://www.google.com/calendar/feeds/default/owncalendars/full", + "https://www.googleapis.com/calendar/v3/calendars", GDATA_ENTRY (calendar), NULL, NULL)); g_assert (GDATA_IS_CALENDAR_CALENDAR (data->calendar)); g_object_unref (calendar); @@ -70,75 +77,48 @@ tear_down_temp_calendar (TempCalendarData *data, gconstpointer service) static void test_authentication (void) { - gboolean retval; - GDataClientLoginAuthorizer *authorizer; - GError *error = NULL; + GDataOAuth2Authorizer *authorizer = NULL; /* owned */ + gchar *authentication_uri, *authorisation_code; gdata_test_mock_server_start_trace (mock_server, "authentication"); - /* Create an authorizer */ - authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_CALENDAR_SERVICE); + authorizer = gdata_oauth2_authorizer_new (CLIENT_ID, CLIENT_SECRET, + REDIRECT_URI, + GDATA_TYPE_CALENDAR_SERVICE); - g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID); + /* Get an authentication URI. */ + authentication_uri = gdata_oauth2_authorizer_build_authentication_uri (authorizer, NULL, FALSE); + g_assert (authentication_uri != NULL); - /* Log in */ - retval = gdata_client_login_authorizer_authenticate (authorizer, USERNAME, PASSWORD, NULL, &error); - g_assert_no_error (error); - g_assert (retval == TRUE); - g_clear_error (&error); + /* Get the authorisation code off the user. */ + if (uhm_server_get_enable_online (mock_server)) { + authorisation_code = gdata_test_query_user_for_verifier (authentication_uri); + } else { + /* Hard coded, extracted from the trace file. TODO */ + authorisation_code = g_strdup ("4/OEX-S1iMbOA_dOnNgUlSYmGWh3TK.QrR73axcNMkWoiIBeO6P2m_su7cwkQI"); + } - /* Check all is as it should be */ - g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, USERNAME); - g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD); + g_free (authentication_uri); + if (authorisation_code == NULL) { + /* Skip tests. */ + goto skip_test; + } + + /* Authorise the token */ + g_assert (gdata_oauth2_authorizer_request_authorization (authorizer, authorisation_code, NULL, NULL) == TRUE); + + /* Check all is as it should be */ g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer), gdata_calendar_service_get_primary_authorization_domain ()) == TRUE); +skip_test: + g_free (authorisation_code); g_object_unref (authorizer); uhm_server_end_trace (mock_server); } -GDATA_ASYNC_TEST_FUNCTIONS (authentication, void, -G_STMT_START { - GDataClientLoginAuthorizer *authorizer; - - /* Create an authorizer */ - authorizer = gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_CALENDAR_SERVICE); - - g_assert_cmpstr (gdata_client_login_authorizer_get_client_id (authorizer), ==, CLIENT_ID); - - gdata_client_login_authorizer_authenticate_async (authorizer, USERNAME, PASSWORD, cancellable, async_ready_callback, async_data); - - g_object_unref (authorizer); -} G_STMT_END, -G_STMT_START { - gboolean retval; - GDataClientLoginAuthorizer *authorizer = GDATA_CLIENT_LOGIN_AUTHORIZER (obj); - - retval = gdata_client_login_authorizer_authenticate_finish (authorizer, async_result, &error); - - if (error == NULL) { - g_assert (retval == TRUE); - - /* Check all is as it should be */ - g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, USERNAME); - g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, PASSWORD); - - g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer), - gdata_calendar_service_get_primary_authorization_domain ()) == TRUE); - } else { - g_assert (retval == FALSE); - - /* Check nothing's changed */ - g_assert_cmpstr (gdata_client_login_authorizer_get_username (authorizer), ==, NULL); - g_assert_cmpstr (gdata_client_login_authorizer_get_password (authorizer), ==, NULL); - - g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer), - gdata_calendar_service_get_primary_authorization_domain ()) == FALSE); - } -} G_STMT_END); - typedef struct { GDataCalendarCalendar *calendar1; GDataCalendarCalendar *calendar2; @@ -160,7 +140,7 @@ set_up_query_calendars (QueryCalendarsData *data, gconstpointer service) gdata_calendar_calendar_set_color (calendar, &colour); data->calendar1 = GDATA_CALENDAR_CALENDAR (gdata_service_insert_entry (GDATA_SERVICE (service), gdata_calendar_service_get_primary_authorization_domain (), - "https://www.google.com/calendar/feeds/default/owncalendars/full", + "https://www.googleapis.com/calendar/v3/calendars", GDATA_ENTRY (calendar), NULL, NULL)); g_assert (GDATA_IS_CALENDAR_CALENDAR (data->calendar1)); g_object_unref (calendar); @@ -170,7 +150,7 @@ set_up_query_calendars (QueryCalendarsData *data, gconstpointer service) gdata_calendar_calendar_set_color (calendar, &colour); data->calendar2 = GDATA_CALENDAR_CALENDAR (gdata_service_insert_entry (GDATA_SERVICE (service), gdata_calendar_service_get_primary_authorization_domain (), - "https://www.google.com/calendar/feeds/default/owncalendars/full", + "https://www.googleapis.com/calendar/v3/calendars", GDATA_ENTRY (calendar), NULL, NULL)); g_assert (GDATA_IS_CALENDAR_CALENDAR (data->calendar2)); g_object_unref (calendar); @@ -340,6 +320,8 @@ static void set_up_query_events (QueryEventsData *data, gconstpointer service) { GDataCalendarEvent *event; + GDataGDWhen *when; + GError *error = NULL; /* Set up a temporary calendar */ set_up_temp_calendar ((TempCalendarData*) data, service); @@ -349,19 +331,37 @@ set_up_query_events (QueryEventsData *data, gconstpointer service) /* Add some test events to it */ event = gdata_calendar_event_new (NULL); gdata_entry_set_title (GDATA_ENTRY (event), "Test Event 1"); - data->event1 = gdata_calendar_service_insert_event (GDATA_CALENDAR_SERVICE (service), event, NULL, NULL); + + when = gdata_gd_when_new (1419113727, 1419113728, FALSE); + gdata_calendar_event_add_time (event, when); + g_object_unref (when); + + data->event1 = gdata_calendar_service_insert_calendar_event (GDATA_CALENDAR_SERVICE (service), data->parent.calendar, event, NULL, &error); + g_assert_no_error (error); g_assert (GDATA_IS_CALENDAR_EVENT (data->event1)); g_object_unref (event); event = gdata_calendar_event_new (NULL); gdata_entry_set_title (GDATA_ENTRY (event), "Test Event 2"); - data->event2 = gdata_calendar_service_insert_event (GDATA_CALENDAR_SERVICE (service), event, NULL, NULL); + + when = gdata_gd_when_new (1419113000, 1419114000, FALSE); + gdata_calendar_event_add_time (event, when); + g_object_unref (when); + + data->event2 = gdata_calendar_service_insert_calendar_event (GDATA_CALENDAR_SERVICE (service), data->parent.calendar, event, NULL, &error); + g_assert_no_error (error); g_assert (GDATA_IS_CALENDAR_EVENT (data->event2)); g_object_unref (event); event = gdata_calendar_event_new (NULL); gdata_entry_set_title (GDATA_ENTRY (event), "Test Event 3"); - data->event3 = gdata_calendar_service_insert_event (GDATA_CALENDAR_SERVICE (service), event, NULL, NULL); + + when = gdata_gd_when_new (1419110000, 1419120000, TRUE); + gdata_calendar_event_add_time (event, when); + g_object_unref (when); + + data->event3 = gdata_calendar_service_insert_calendar_event (GDATA_CALENDAR_SERVICE (service), data->parent.calendar, event, NULL, &error); + g_assert_no_error (error); g_assert (GDATA_IS_CALENDAR_EVENT (data->event3)); g_object_unref (event); @@ -519,7 +519,8 @@ test_event_insert (InsertEventData *data, gconstpointer service) g_object_unref (when); /* Insert the event */ - new_event = data->new_event = gdata_calendar_service_insert_event (GDATA_CALENDAR_SERVICE (service), event, NULL, &error); + new_event = data->new_event = gdata_calendar_service_insert_calendar_event (GDATA_CALENDAR_SERVICE (service), + data->parent.calendar, event, NULL, &error); g_assert_no_error (error); g_assert (GDATA_IS_CALENDAR_EVENT (new_event)); g_clear_error (&error); @@ -561,7 +562,9 @@ G_STMT_START { g_object_unref (when); /* Insert the event */ - gdata_calendar_service_insert_event_async (GDATA_CALENDAR_SERVICE (service), event, cancellable, async_ready_callback, async_data); + gdata_calendar_service_insert_calendar_event_async (GDATA_CALENDAR_SERVICE (service), + data->parent.calendar, event, cancellable, + async_ready_callback, async_data); g_object_unref (event); } G_STMT_END, @@ -578,7 +581,7 @@ G_STMT_START { } G_STMT_END); static void -test_event_xml (void) +test_event_json (void) { GDataCalendarEvent *event; GDataGDWhere *where; @@ -604,174 +607,204 @@ test_event_xml (void) gdata_calendar_event_add_time (event, when); g_object_unref (when); - /* Check the XML */ - gdata_test_assert_xml (event, - "<?xml version='1.0' encoding='UTF-8'?>" - "<entry xmlns='http://www.w3.org/2005/Atom' " - "xmlns:gd='http://schemas.google.com/g/2005' " - "xmlns:gCal='http://schemas.google.com/gCal/2005' " - "xmlns:app='http://www.w3.org/2007/app'>" - "<title type='text'>Tennis with Beth</title>" - "<content type='text'>Meet for a quick lesson.</content>" - "<category term='http://schemas.google.com/g/2005#event' scheme='http://schemas.google.com/g/2005#kind'/>" - "<gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'/>" - "<gd:transparency value='http://schemas.google.com/g/2005#event.opaque'/>" - "<gCal:guestsCanModify value='false'/>" - "<gCal:guestsCanInviteOthers value='false'/>" - "<gCal:guestsCanSeeGuests value='false'/>" - "<gCal:anyoneCanAddSelf value='false'/>" - "<gd:when startTime='2009-04-17T15:00:00Z' endTime='2009-04-17T17:00:00Z'/>" - "<gd:who email='john.smith@example.com' " - "rel='http://schemas.google.com/g/2005#event.organizer' " - "valueString='John Smith\342\200\275'/>" - "<gd:where valueString='Rolling Lawn Courts'/>" - "</entry>"); + /* Check the JSON */ + gdata_test_assert_json (event, "{" + "'summary': 'Tennis with Beth'," + "'description': 'Meet for a quick lesson.'," + "'kind': 'calendar#event'," + "'status': 'confirmed'," + "'transparency': 'opaque'," + "'guestsCanModify': false," + "'guestsCanInviteOthers': false," + "'guestsCanSeeOtherGuests': false," + "'anyoneCanAddSelf': false," + "'start': {" + "'dateTime': '2009-04-17T15:00:00Z'," + "'timeZone': 'UTC'" + "}," + "'end': {" + "'dateTime': '2009-04-17T17:00:00Z'," + "'timeZone': 'UTC'" + "}," + "'attendees': [" + "{" + "'email': 'john.smith@example.com'," + "'displayName': 'John Smith‽'," + "'organizer': true" + "}" + "]," + "'organizer': {" + "'email': 'john.smith@example.com'," + "'displayName': 'John Smith‽'" + "}," + "'location': 'Rolling Lawn Courts'" + "}"); } static void -test_event_xml_dates (void) +test_event_json_dates (void) { - GDataCalendarEvent *event; - GList *i; - GDataGDWhen *when; - gint64 _time; - GError *error = NULL; - - event = GDATA_CALENDAR_EVENT (gdata_parsable_new_from_xml (GDATA_TYPE_CALENDAR_EVENT, - "<entry xmlns='http://www.w3.org/2005/Atom' " - "xmlns:gd='http://schemas.google.com/g/2005' " - "xmlns:gCal='http://schemas.google.com/gCal/2005' " - "xmlns:app='http://www.w3.org/2007/app'>" - "<title type='text'>Tennis with Beth</title>" - "<content type='text'>Meet for a quick lesson.</content>" - "<category term='http://schemas.google.com/g/2005#event' scheme='http://schemas.google.com/g/2005#kind'/>" - "<gd:when startTime='2009-04-17'/>" - "<gd:when startTime='2009-04-17T15:00:00Z'/>" - "<gd:when startTime='2009-04-27' endTime='20090506'/>" - "</entry>", -1, &error)); - g_assert_no_error (error); - g_assert (GDATA_IS_ENTRY (event)); - g_clear_error (&error); - - /* Check the times */ - i = gdata_calendar_event_get_times (event); - - /* First time */ - when = GDATA_GD_WHEN (i->data); - g_assert (i->next != NULL); - g_assert (gdata_gd_when_is_date (when) == TRUE); - _time = gdata_gd_when_get_start_time (when); - g_assert_cmpint (_time, ==, 1239926400); - _time = gdata_gd_when_get_end_time (when); - g_assert_cmpint (_time, ==, -1); - g_assert (gdata_gd_when_get_value_string (when) == NULL); - g_assert (gdata_gd_when_get_reminders (when) == NULL); - - /* Second time */ - i = i->next; - when = GDATA_GD_WHEN (i->data); - g_assert (i->next != NULL); - g_assert (gdata_gd_when_is_date (when) == FALSE); - _time = gdata_gd_when_get_start_time (when); - g_assert_cmpint (_time, ==, 1239926400 + 54000); - _time = gdata_gd_when_get_end_time (when); - g_assert_cmpint (_time, ==, -1); - g_assert (gdata_gd_when_get_value_string (when) == NULL); - g_assert (gdata_gd_when_get_reminders (when) == NULL); - - /* Third time */ - i = i->next; - when = GDATA_GD_WHEN (i->data); - g_assert (i->next == NULL); - g_assert (gdata_gd_when_is_date (when) == TRUE); - _time = gdata_gd_when_get_start_time (when); - g_assert_cmpint (_time, ==, 1239926400 + 864000); - _time = gdata_gd_when_get_end_time (when); - g_assert_cmpint (_time, ==, 1241568000); - g_assert (gdata_gd_when_get_value_string (when) == NULL); - g_assert (gdata_gd_when_get_reminders (when) == NULL); - - /* Check the XML */ - gdata_test_assert_xml (event, - "<?xml version='1.0' encoding='UTF-8'?>" - "<entry xmlns='http://www.w3.org/2005/Atom' " - "xmlns:gd='http://schemas.google.com/g/2005' " - "xmlns:gCal='http://schemas.google.com/gCal/2005' " - "xmlns:app='http://www.w3.org/2007/app'>" - "<title type='text'>Tennis with Beth</title>" - "<content type='text'>Meet for a quick lesson.</content>" - "<category term='http://schemas.google.com/g/2005#event' scheme='http://schemas.google.com/g/2005#kind'/>" - "<gCal:guestsCanModify value='false'/>" - "<gCal:guestsCanInviteOthers value='false'/>" - "<gCal:guestsCanSeeGuests value='false'/>" - "<gCal:anyoneCanAddSelf value='false'/>" - "<gd:when startTime='2009-04-17'/>" - "<gd:when startTime='2009-04-17T15:00:00Z'/>" - "<gd:when startTime='2009-04-27' endTime='2009-05-06'/>" - "</entry>"); - - g_object_unref (event); + guint i; + + const struct { + const gchar *json; + gboolean is_date; + gint64 start_time; + gint64 end_time; + const gchar *output_json; /* NULL if equal to @json */ + } test_vectors[] = { + /* Plain date, single day. */ + { "'start': {" + "'date': '2009-04-17'," + "'timeZone': 'UTC'" + "}," + "'end': {" + "'date': '2009-04-18'," + "'timeZone': 'UTC'" + "}", TRUE, 1239926400, 1239926400 + 86400, NULL }, + /* Full date and time. */ + { "'start': {" + "'dateTime': '2009-04-17T15:00:00Z'," + "'timeZone': 'UTC'" + "}," + "'end': {" + "'dateTime': '2009-04-17T16:00:00Z'," + "'timeZone': 'UTC'" + "}", FALSE, 1239926400 + 54000, 1239926400 + 54000 + 3600, NULL }, + /* Start and end time. */ + { "'start': {" + "'date': '2009-04-27'," + "'timeZone': 'UTC'" + "}," + "'end': {" + "'date': '20090506'," + "'timeZone': 'UTC'" + "}", TRUE, 1239926400 + 864000, 1241568000, "'start': {" + "'date': '2009-04-27'," + "'timeZone': 'UTC'" + "}," + "'end': {" + "'date': '2009-05-06'," + "'timeZone': 'UTC'" + "}" }, + }; + + for (i = 0; i < G_N_ELEMENTS (test_vectors); i++) { + gchar *json = NULL, *output_json = NULL; /* owned */ + GDataCalendarEvent *event; + GList *j; + GDataGDWhen *when; + gint64 _time; + GError *error = NULL; + + json = g_strdup_printf ("{" + "'summary': 'Tennis with Beth'," + "'description': 'Meet for a quick lesson.'," + "'kind': 'calendar#event'," + "%s" + "}", test_vectors[i].json); + output_json = g_strdup_printf ("{" + "'summary': 'Tennis with Beth'," + "'description': 'Meet for a quick lesson.'," + "'kind': 'calendar#event'," + "'guestsCanModify': false," + "'guestsCanInviteOthers': false," + "'guestsCanSeeOtherGuests': false," + "'anyoneCanAddSelf': false," + "'attendees': []," + "%s" + "}", (test_vectors[i].output_json != NULL) ? test_vectors[i].output_json : test_vectors[i].json); + + event = GDATA_CALENDAR_EVENT (gdata_parsable_new_from_json (GDATA_TYPE_CALENDAR_EVENT, json, -1, &error)); + g_assert_no_error (error); + g_assert (GDATA_IS_ENTRY (event)); + g_clear_error (&error); + + /* Check the times */ + j = gdata_calendar_event_get_times (event); + g_assert (j != NULL); + + when = GDATA_GD_WHEN (j->data); + g_assert (gdata_gd_when_is_date (when) == test_vectors[i].is_date); + _time = gdata_gd_when_get_start_time (when); + g_assert_cmpint (_time, ==, test_vectors[i].start_time); + _time = gdata_gd_when_get_end_time (when); + g_assert_cmpint (_time, ==, test_vectors[i].end_time); + g_assert (gdata_gd_when_get_value_string (when) == NULL); + g_assert (gdata_gd_when_get_reminders (when) == NULL); + + /* Should be no other times. */ + g_assert (j->next == NULL); + + /* Check the JSON */ + gdata_test_assert_json (event, output_json); + + g_object_unref (event); + g_free (output_json); + g_free (json); + } } static void -test_event_xml_recurrence (void) +test_event_json_recurrence (void) { GDataCalendarEvent *event; GError *error = NULL; gchar *id, *uri; - event = GDATA_CALENDAR_EVENT (gdata_parsable_new_from_xml (GDATA_TYPE_CALENDAR_EVENT, - "<entry xmlns='http://www.w3.org/2005/Atom' " - "xmlns:gd='http://schemas.google.com/g/2005' " - "xmlns:gCal='http://schemas.google.com/gCal/2005' " - "xmlns:app='http://www.w3.org/2007/app'>" - "<id>http://www.google.com/calendar/feeds/libgdata.test@googlemail.com/events/g5928e82rrch95b25f8ud0dlsg_20090429T153000Z</id>" - "<published>2009-04-25T15:22:47.000Z</published>" - "<updated>2009-04-27T17:54:10.000Z</updated>" - "<app:edited xmlns:app='http://www.w3.org/2007/app'>2009-04-27T17:54:10.000Z</app:edited>" - "<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/>" - "<title>Test daily instance event</title>" - "<content></content>" - "<link rel='http://www.iana.org/assignments/relation/alternate' type='text/html' " - "href='http://www.google.com/calendar/event?" - "eid=ZzU5MjhlODJycmNoOTViMjVmOHVkMGRsc2dfMjAwOTA0MjlUMTUzMDAwWiBsaWJnZGF0YS50ZXN0QGdvb2dsZW1haWwuY29t' " - "title='alternate'/>" - "<link rel='http://www.iana.org/assignments/relation/self' type='application/atom+xml' " - "href='http://www.google.com/calendar/feeds/libgdata.test@googlemail.com/private/full/" - "g5928e82rrch95b25f8ud0dlsg_20090429T153000Z'/>" - "<link rel='http://www.iana.org/assignments/relation/edit' type='application/atom+xml' " - "href='http://www.google.com/calendar/feeds/libgdata.test@googlemail.com/private/full/" - "g5928e82rrch95b25f8ud0dlsg_20090429T153000Z'/>" - "<author>" - "<name>GData Test</name>" - "<email>libgdata.test@googlemail.com</email>" - "</author>" - "<gd:originalEvent id='g5928e82rrch95b25f8ud0dlsg' " - "href='http://www.google.com/calendar/feeds/libgdata.test@googlemail.com/private/full/" - "g5928e82rrch95b25f8ud0dlsg'>" - "<gd:when startTime='2009-04-29T16:30:00.000+01:00'/>" - "</gd:originalEvent>" - "<gCal:guestsCanModify value='false'/>" - "<gCal:guestsCanInviteOthers value='false'/>" - "<gCal:guestsCanSeeGuests value='false'/>" - "<gCal:anyoneCanAddSelf value='false'/>" - "<gd:comments>" - "<gd:feedLink href='http://www.google.com/calendar/feeds/libgdata.test@googlemail.com/private/full/" - "g5928e82rrch95b25f8ud0dlsg_20090429T153000Z/comments'/>" - "</gd:comments>" - "<gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'/>" - "<gd:visibility value='http://schemas.google.com/g/2005#event.private'/>" - "<gd:transparency value='http://schemas.google.com/g/2005#event.opaque'/>" - "<gCal:uid value='g5928e82rrch95b25f8ud0dlsg@google.com'/>" - "<gCal:sequence value='0'/>" - "<gd:when startTime='2009-04-29T17:30:00.000+01:00' endTime='2009-04-29T17:30:00.000+01:00'>" - "<gd:reminder minutes='10' method='email'/>" - "<gd:reminder minutes='10' method='alert'/>" - "</gd:when>" - "<gd:who rel='http://schemas.google.com/g/2005#event.organizer' valueString='GData Test' " - "email='libgdata.test@googlemail.com'/>" - "<gd:where valueString=''/>" - "</entry>", -1, &error)); + event = GDATA_CALENDAR_EVENT (gdata_parsable_new_from_json (GDATA_TYPE_CALENDAR_EVENT, "{" + "'id': 'https://www.googleapis.com/calendar/v3/calendars/libgdata.test@googlemail.com/events/g5928e82rrch95b25f8ud0dlsg_20090429T153000Z'," + "'updated': '2009-04-27T17:54:10.000Z'," + "'summary': 'Test daily instance event'," + "'kind': 'calendar#event'," + "'creator': {" + "'displayName': 'GData Test'," + "'email': 'libgdata.test@googlemail.com'" + "}," + "'recurringEventId': 'g5928e82rrch95b25f8ud0dlsg'," + "'originalStartTime': {" + "'dateTime': '2009-04-29T16:30:00.000+01:00'," + "'timeZone': 'UTC'" + "}," + "'guestsCanModify': false," + "'guestsCanInviteOthers': false," + "'guestsCanSeeOtherGuests': false," + "'anyoneCanAddSelf': false," + "'status': 'confirmed'," + "'visibility': 'private'," + "'transparency': 'opaque'," + "'iCalUID': 'g5928e82rrch95b25f8ud0dlsg@google.com'," + "'sequence': '0'," + "'start': {" + "'dateTime': '2009-04-29T17:30:00.000+01:00'," + "'timeZone': 'UTC'" + "}," + "'end': {" + "'dateTime': '2009-04-29T17:30:00.000+01:00'," + "'timeZone': 'UTC'" + "}," + "'reminders': {" + "'overrides': [{" + "'method': 'email'," + "'minutes': 10" + "}, {" + "'method': 'popup'," + "'minutes': 10" + "}]" + "}," + "'attendees': [" + "{" + "'email': 'libgdata.test@googlemail.com'," + "'displayName': 'GData Test'," + "'organizer': true," + "'responseStatus': 'needsAction'" + "}" + "]," + "'organizer': {" + "'email': 'libgdata.test@googlemail.com'," + "'displayName': 'GData Test'" + "}" + "}", -1, &error)); g_assert_no_error (error); g_assert (GDATA_IS_ENTRY (event)); g_clear_error (&error); @@ -781,7 +814,7 @@ test_event_xml_recurrence (void) gdata_calendar_event_get_original_event_details (event, &id, &uri); g_assert_cmpstr (id, ==, "g5928e82rrch95b25f8ud0dlsg"); - g_assert_cmpstr (uri, ==, "http://www.google.com/calendar/feeds/libgdata.test@googlemail.com/private/full/g5928e82rrch95b25f8ud0dlsg"); + g_assert_cmpstr (uri, ==, "https://www.googleapis.com/calendar/v3/events/g5928e82rrch95b25f8ud0dlsg"); g_free (id); g_free (uri); @@ -796,18 +829,14 @@ test_calendar_escaping (void) calendar = gdata_calendar_calendar_new (NULL); gdata_calendar_calendar_set_timezone (calendar, "<timezone>"); - /* Check the outputted XML is escaped properly */ - gdata_test_assert_xml (calendar, - "<?xml version='1.0' encoding='UTF-8'?>" - "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005' " - "xmlns:gCal='http://schemas.google.com/gCal/2005' xmlns:app='http://www.w3.org/2007/app'>" - "<title type='text'></title>" - "<category term='http://schemas.google.com/gCal/2005#calendarmeta' scheme='http://schemas.google.com/g/2005#kind'/>" - "<gCal:timezone value='<timezone>'/>" - "<gCal:hidden value='false'/>" - "<gCal:color value='#000000'/>" - "<gCal:selected value='false'/>" - "</entry>"); + /* Check the outputted JSON is escaped properly */ + gdata_test_assert_json (calendar, "{" + "'kind': 'calendar#calendar'," + "'timeZone': '<timezone>'," + "'hidden': false," + "'backgroundColor': '#000000'," + "'selected': false" + "}"); g_object_unref (calendar); } @@ -817,29 +846,90 @@ test_event_escaping (void) GDataCalendarEvent *event; event = gdata_calendar_event_new (NULL); - gdata_calendar_event_set_status (event, "<status>"); - gdata_calendar_event_set_visibility (event, "<visibility>"); - gdata_calendar_event_set_transparency (event, "<transparency>"); - gdata_calendar_event_set_uid (event, "<uid>"); - gdata_calendar_event_set_recurrence (event, "<recurrence>"); - - /* Check the outputted XML is escaped properly */ - gdata_test_assert_xml (event, - "<?xml version='1.0' encoding='UTF-8'?>" - "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005' " - "xmlns:gCal='http://schemas.google.com/gCal/2005' xmlns:app='http://www.w3.org/2007/app'>" - "<title type='text'></title>" - "<category term='http://schemas.google.com/g/2005#event' scheme='http://schemas.google.com/g/2005#kind'/>" - "<gd:eventStatus value='<status>'/>" - "<gd:visibility value='<visibility>'/>" - "<gd:transparency value='<transparency>'/>" - "<gCal:uid value='<uid>'/>" - "<gCal:guestsCanModify value='false'/>" - "<gCal:guestsCanInviteOthers value='false'/>" - "<gCal:guestsCanSeeGuests value='false'/>" - "<gCal:anyoneCanAddSelf value='false'/>" - "<gd:recurrence><recurrence></gd:recurrence>" - "</entry>"); + gdata_calendar_event_set_status (event, "\"status\""); + gdata_calendar_event_set_visibility (event, "\"visibility\""); + gdata_calendar_event_set_transparency (event, "\"transparency\""); + gdata_calendar_event_set_uid (event, "\"uid\""); + gdata_calendar_event_set_recurrence (event, "\"recurrence\""); + + /* Check the outputted JSON is escaped properly */ + gdata_test_assert_json (event, "{" + "'kind': 'calendar#event'," + "'status': '\"status\"'," + "'transparency': '\"transparency\"'," + "'visibility': '\"visibility\"'," + "'iCalUID': '\"uid\"'," + "'recurrence': [ '\"recurrence\"' ]," + "'guestsCanModify': false," + "'guestsCanInviteOthers': false," + "'guestsCanSeeOtherGuests': false," + "'anyoneCanAddSelf': false," + "'attendees': []" + "}"); + g_object_unref (event); +} + +/* Test the event parser with the minimal number of properties specified. */ +static void +test_calendar_event_parser_minimal (void) +{ + GDataCalendarEvent *event = NULL; /* owned */ + GDataEntry *entry; /* unowned */ + GDataLink *self_link; /* unowned */ + GError *error = NULL; + + event = GDATA_CALENDAR_EVENT (gdata_parsable_new_from_json (GDATA_TYPE_CALENDAR_EVENT, + "{" + "\"kind\": \"calendar#event\"," + "\"etag\": \"\\\"2838230136828000\\\"\"," + "\"id\": \"hsfgtc50u68vdai81t6634u7lg\"," + "\"status\": \"confirmed\"," + "\"htmlLink\": \"https://www.google.com/calendar/event?eid=aHNmZ3RjNTB1Njh2ZGFpODF0NjYzNHU3bGcgODk5MWkzNjM0YzRzN3Nwa3NrcjNjZjVuanNAZw\"," + "\"created\": \"2014-12-20T22:37:48.000Z\"," + "\"updated\": \"2014-12-20T22:37:48.414Z\"," + "\"summary\": \"Test Event 1\"," + "\"creator\": {" + "\"email\": \"libgdata.test@googlemail.com\"," + "\"displayName\": \"GData Test\"" + "}," + "\"organizer\": {" + "\"email\": \"8991i3634c4s7spkskr3cf5njs@group.calendar.google.com\"," + "\"displayName\": \"Temp Test Calendar\"," + "\"self\": true" + "}," + "\"start\": {" + "\"dateTime\": \"2014-12-20T22:15:27Z\"," + "\"timeZone\": \"UTC\"" + "}," + "\"end\": {" + "\"dateTime\": \"2014-12-20T22:15:28Z\"," + "\"timeZone\": \"UTC\"" + "}," + "\"iCalUID\": \"hsfgtc50u68vdai81t6634u7lg@google.com\"," + "\"sequence\": 0," + "\"guestsCanInviteOthers\": false," + "\"guestsCanSeeOtherGuests\": false," + "\"reminders\": {" + "\"useDefault\": true" + "}" + "}", -1, &error)); + g_assert_no_error (error); + g_assert (GDATA_IS_CALENDAR_EVENT (event)); + gdata_test_compare_kind (GDATA_ENTRY (event), "calendar#event", NULL); + + entry = GDATA_ENTRY (event); + + /* Check the event’s properties. */ + g_assert_cmpstr (gdata_entry_get_id (entry), ==, + "hsfgtc50u68vdai81t6634u7lg"); + g_assert_cmpstr (gdata_entry_get_etag (entry), ==, + "\"2838230136828000\""); + g_assert_cmpstr (gdata_entry_get_title (entry), ==, + "Test Event 1"); + g_assert_cmpint (gdata_entry_get_updated (entry), ==, 1419115068); + + /* TODO: check everything else */ + g_object_unref (event); } @@ -861,7 +951,7 @@ test_access_rule_properties (void) } static void -test_access_rule_xml (void) +test_access_rule_json (void) { GDataAccessRule *rule; @@ -870,8 +960,8 @@ test_access_rule_xml (void) gdata_access_rule_set_role (rule, GDATA_CALENDAR_ACCESS_ROLE_EDITOR); gdata_access_rule_set_scope (rule, GDATA_ACCESS_SCOPE_USER, "darcy@gmail.com"); - /* Check the XML */ - gdata_test_assert_xml (rule, + /* Check the JSON */ + gdata_test_assert_json (rule, "<?xml version='1.0' encoding='UTF-8'?>" "<entry xmlns='http://www.w3.org/2005/Atom' " "xmlns:gd='http://schemas.google.com/g/2005' " @@ -1442,7 +1532,66 @@ mock_server_notify_resolver_cb (GObject *object, GParamSpec *pspec, gpointer use const gchar *ip_address = uhm_server_get_address (server); uhm_resolver_add_A (resolver, "www.google.com", ip_address); + uhm_resolver_add_A (resolver, "www.googleapis.com", ip_address); + uhm_resolver_add_A (resolver, + "accounts.google.com", ip_address); + } +} + +/* Set up a global GDataAuthorizer to be used for all the tests. Unfortunately, + * the Google Calendar API is limited to OAuth1 and OAuth2 authorisation, so + * this requires user interaction when online. + * + * If not online, use a dummy authoriser. */ +static GDataAuthorizer * +create_global_authorizer (void) +{ + GDataOAuth2Authorizer *authorizer = NULL; /* owned */ + gchar *authentication_uri, *authorisation_code; + GError *error = NULL; + + /* If not online, just return a dummy authoriser. */ + if (!uhm_server_get_enable_online (mock_server)) { + return GDATA_AUTHORIZER (gdata_dummy_authorizer_new (GDATA_TYPE_CALENDAR_SERVICE)); + } + + /* Otherwise, go through the interactive OAuth dance. */ + gdata_test_mock_server_start_trace (mock_server, "global-authentication"); + authorizer = gdata_oauth2_authorizer_new (CLIENT_ID, CLIENT_SECRET, + REDIRECT_URI, + GDATA_TYPE_CALENDAR_SERVICE); + + /* Get an authentication URI */ + authentication_uri = gdata_oauth2_authorizer_build_authentication_uri (authorizer, NULL, FALSE); + g_assert (authentication_uri != NULL); + + /* Get the authorisation code off the user. */ + if (uhm_server_get_enable_online (mock_server)) { + authorisation_code = gdata_test_query_user_for_verifier (authentication_uri); + } else { + /* Hard coded, extracted from the trace file. TODO */ + authorisation_code = g_strdup ("4/hmXZtrXmXMqK1hwiWPZs9F_N6DK-.Ap4OICAUIe0WoiIBeO6P2m8IDoMxkQI"); + } + + g_free (authentication_uri); + + if (authorisation_code == NULL) { + /* Skip tests. */ + g_object_unref (authorizer); + authorizer = NULL; + goto skip_test; } + + /* Authorise the token */ + g_assert (gdata_oauth2_authorizer_request_authorization (authorizer, authorisation_code, NULL, &error)); + g_assert_no_error (error); + +skip_test: + g_free (authorisation_code); + + uhm_server_end_trace (mock_server); + + return GDATA_AUTHORIZER (authorizer); } int @@ -1460,19 +1609,12 @@ main (int argc, char *argv[]) trace_directory = g_file_new_for_path (TEST_FILE_DIR "traces/calendar"); uhm_server_set_trace_directory (mock_server, trace_directory); g_object_unref (trace_directory); - - gdata_test_mock_server_start_trace (mock_server, "global-authentication"); - authorizer = GDATA_AUTHORIZER (gdata_client_login_authorizer_new (CLIENT_ID, GDATA_TYPE_CALENDAR_SERVICE)); - gdata_client_login_authorizer_authenticate (GDATA_CLIENT_LOGIN_AUTHORIZER (authorizer), USERNAME, PASSWORD, NULL, NULL); - uhm_server_end_trace (mock_server); +#if 0 + authorizer = create_global_authorizer (); service = GDATA_SERVICE (gdata_calendar_service_new (authorizer)); g_test_add_func ("/calendar/authentication", test_authentication); - g_test_add ("/calendar/authentication/async", GDataAsyncTestData, NULL, gdata_set_up_async_test_data, test_authentication_async, - gdata_tear_down_async_test_data); - g_test_add ("/calendar/authentication/async/cancellation", GDataAsyncTestData, NULL, gdata_set_up_async_test_data, - test_authentication_async_cancellation, gdata_tear_down_async_test_data); g_test_add ("/calendar/query/all_calendars", QueryCalendarsData, service, set_up_query_calendars, test_query_all_calendars, tear_down_query_calendars); @@ -1514,21 +1656,29 @@ main (int argc, char *argv[]) tear_down_temp_calendar_acls); g_test_add ("/calendar/access-rule/delete", TempCalendarAclsData, service, set_up_temp_calendar_acls, test_access_rule_delete, tear_down_temp_calendar_acls); - +#endif +#if 0 +TODO g_test_add_data_func ("/calendar/batch", service, test_batch); g_test_add ("/calendar/batch/async", BatchAsyncData, service, setup_batch_async, test_batch_async, teardown_batch_async); g_test_add ("/calendar/batch/async/cancellation", BatchAsyncData, service, setup_batch_async, test_batch_async_cancellation, teardown_batch_async); +#endif - g_test_add_func ("/calendar/event/xml", test_event_xml); - g_test_add_func ("/calendar/event/xml/dates", test_event_xml_dates); - g_test_add_func ("/calendar/event/xml/recurrence", test_event_xml_recurrence); + g_test_add_func ("/calendar/event/json", test_event_json); + g_test_add_func ("/calendar/event/json/dates", test_event_json_dates); + g_test_add_func ("/calendar/event/json/recurrence", test_event_json_recurrence); g_test_add_func ("/calendar/event/escaping", test_event_escaping); + g_test_add_func ("/calendar/event/parser/minimal", + test_calendar_event_parser_minimal); g_test_add_func ("/calendar/calendar/escaping", test_calendar_escaping); +#if 0 +TODO g_test_add_func ("/calendar/access-rule/properties", test_access_rule_properties); - g_test_add_func ("/calendar/access-rule/xml", test_access_rule_xml); + g_test_add_func ("/calendar/access-rule/json", test_access_rule_json); +#endif g_test_add_func ("/calendar/query/uri", test_query_uri); g_test_add_func ("/calendar/query/etag", test_query_etag); |