[libgdata/wip/calendar-v3: 2/3] calendar: WIP V3 work



commit 0cc47d6b89ab2140adbe73f973103487caf3c3f8
Author: Philip Withnall <philip tecnocode co uk>
Date:   Sat Dec 20 23:37:44 2014 +0000

    calendar: WIP V3 work

 docs/reference/gdata-sections.txt                 |    2 +
 gdata/gdata-parser.c                              |   72 ++-
 gdata/gdata-parser.h                              |    6 +
 gdata/gdata.symbols                               |    2 +
 gdata/services/calendar/gdata-calendar-calendar.c |   22 +-
 gdata/services/calendar/gdata-calendar-event.c    |  688 ++++++++++++++++-----
 gdata/services/calendar/gdata-calendar-service.c  |  351 ++++++++++-
 gdata/services/calendar/gdata-calendar-service.h  |   14 +
 gdata/tests/calendar.c                            |  111 ++++-
 9 files changed, 1074 insertions(+), 194 deletions(-)
---
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index b3fdcb0..3e55d6f 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-parser.c b/gdata/gdata-parser.c
index bde97c9..cd1d762 100644
--- a/gdata/gdata-parser.c
+++ b/gdata/gdata-parser.c
@@ -306,8 +306,10 @@ gdata_parser_error_not_iso8601_format_json (JsonReader *reader, const gchar *act
        return FALSE;
 }
 
-static gboolean
-parser_error_from_json_error (JsonReader *reader, const GError *json_error, GError **error)
+gboolean
+gdata_parser_error_from_json_error (JsonReader *reader,
+                                    const GError *json_error,
+                                    GError **error)
 {
        g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
                     /* Translators: the parameter is an error message. */
@@ -731,6 +733,64 @@ gdata_parser_object_from_element (xmlNode *element, const gchar *element_name, G
 }
 
 /*
+ * gdata_parser_int_from_json_member:
+ * @reader: #JsonReader cursor object to read JSON node from
+ * @member_name: the name of the member to parse
+ * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
+ * @output: the return location for the parsed integer content
+ * @success: the return location for a value which is %TRUE if the integer was parsed successfully, %FALSE 
if an error was encountered,
+ * and undefined if @element didn't match @element_name
+ * @error: a #GError, or %NULL
+ *
+ * Gets the integer content of @element if its name is @element_name, subject to various checks specified by 
@options.
+ *
+ * If @element doesn't match @element_name, %FALSE will be returned, @error will be unset and @success will 
be unset.
+ *
+ * If @element matches @element_name but one of the checks specified by @options fails, %TRUE will be 
returned, @error will be set to a
+ * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
+ *
+ * If @element matches @element_name and all of the checks specified by @options pass, %TRUE will be 
returned, @error will be unset and
+ * @success will be set to %TRUE.
+ *
+ * The reason for returning the success of the parsing in @success is so that calls to 
gdata_parser_integer_from_element() can be chained
+ * together in a large "or" statement based on their return values, for the purposes of determining whether 
any of the calls matched
+ * a given @element. If any of the calls to gdata_parser_integer_from_element() return %TRUE, the value of 
@success can be examined.
+ *
+ * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
+ *
+ * Since: UNRELEASED
+ */
+gboolean
+gdata_parser_int_from_json_member (JsonReader *reader, const gchar *member_name, GDataParserOptions options,
+                                   gint64 *output, gboolean *success, GError **error)
+{
+       gint64 val;
+       const GError *child_error = NULL;
+
+       /* Check if there's such element */
+       if (g_strcmp0 (json_reader_get_member_name (reader), member_name) != 0) {
+               return FALSE;
+       }
+
+       /* Check if the output int has already been set. The JSON parser guarantees this can't happen. */
+       g_assert (!(options & P_NO_DUPES) || *output == 0);
+
+       /* Get the int and check it for NULLness or emptiness. Check for parser errors first. */
+       val = json_reader_get_int_value (reader);
+       child_error = json_reader_get_error (reader);
+       if (child_error != NULL) {
+               *success = gdata_parser_error_from_json_error (reader, child_error, error);
+               return TRUE;
+       }
+
+       /* Success! */
+       *output = val;
+       *success = TRUE;
+
+       return TRUE;
+}
+
+/*
  * gdata_parser_string_from_json_member:
  * @reader: #JsonReader cursor object to read JSON node from
  * @member_name: the name of the member to parse
@@ -777,7 +837,7 @@ gdata_parser_string_from_json_member (JsonReader *reader, const gchar *member_na
        text = json_reader_get_string_value (reader);
        child_error = json_reader_get_error (reader);
        if (child_error != NULL) {
-               *success = parser_error_from_json_error (reader, child_error, error);
+               *success = gdata_parser_error_from_json_error (reader, child_error, error);
                return TRUE;
        } else if ((options & P_REQUIRED && text == NULL) || (options & P_NON_EMPTY && text != NULL && *text 
== '\0')) {
                *success = gdata_parser_error_required_json_content_missing (reader, error);
@@ -843,7 +903,7 @@ gdata_parser_int64_time_from_json_member (JsonReader *reader, const gchar *membe
        text = json_reader_get_string_value (reader);
        child_error = json_reader_get_error (reader);
        if (child_error != NULL) {
-               *success = parser_error_from_json_error (reader, child_error, error);
+               *success = gdata_parser_error_from_json_error (reader, child_error, error);
                return TRUE;
        } else if (options & P_REQUIRED && (text == NULL || *text == '\0')) {
                *success = gdata_parser_error_required_json_content_missing (reader, error);
@@ -906,7 +966,7 @@ gdata_parser_boolean_from_json_member (JsonReader *reader, const gchar *member_n
        val = json_reader_get_boolean_value (reader);
        child_error = json_reader_get_error (reader);
        if (child_error != NULL) {
-               *success = parser_error_from_json_error (reader, child_error, error);
+               *success = gdata_parser_error_from_json_error (reader, child_error, error);
                return TRUE;
        }
 
@@ -976,7 +1036,7 @@ gdata_parser_color_from_json_member (JsonReader *reader,
        text = json_reader_get_string_value (reader);
        child_error = json_reader_get_error (reader);
        if (child_error != NULL) {
-               *success = parser_error_from_json_error (reader, child_error, error);
+               *success = gdata_parser_error_from_json_error (reader, child_error, error);
                return TRUE;
        } else if (options & P_REQUIRED && (text == NULL || *text == '\0')) {
                *success = gdata_parser_error_required_json_content_missing (reader, error);
diff --git a/gdata/gdata-parser.h b/gdata/gdata-parser.h
index 31567f7..a35ecdf 100644
--- a/gdata/gdata-parser.h
+++ b/gdata/gdata-parser.h
@@ -39,6 +39,10 @@ gboolean gdata_parser_error_duplicate_element (xmlNode *element, GError **error)
 gboolean gdata_parser_error_duplicate_json_element (JsonReader *reader, GError **error);
 gboolean gdata_parser_error_required_json_content_missing (JsonReader *reader, GError **error);
 gboolean gdata_parser_error_not_iso8601_format_json (JsonReader *reader, const gchar *actual_value, GError 
**error);
+gboolean
+gdata_parser_error_from_json_error (JsonReader *reader,
+                                    const GError *json_error,
+                                    GError **error);
 
 gboolean gdata_parser_int64_from_date (const gchar *date, gint64 *_time);
 gchar *gdata_parser_date_from_int64 (gint64 _time) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
@@ -87,6 +91,8 @@ gboolean gdata_parser_object_from_element (xmlNode *element, const gchar *elemen
                                            gpointer /* GDataParsable ** */ _output, gboolean *success, 
GError **error);
 gboolean gdata_parser_string_from_json_member (JsonReader *reader, const gchar *member_name, 
GDataParserOptions options,
                                                gchar **output, gboolean *success, GError **error);
+gboolean gdata_parser_int_from_json_member (JsonReader *reader, const gchar *member_name, GDataParserOptions 
options,
+                                            gint64 *output, gboolean *success, GError **error);
 gboolean gdata_parser_int64_time_from_json_member (JsonReader *reader, const gchar *member_name, 
GDataParserOptions options,
                                                    gint64 *output, gboolean *success, GError **error);
 gboolean gdata_parser_boolean_from_json_member (JsonReader *reader, const gchar *member_name, 
GDataParserOptions options,
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 7ae3e95..c71cf90 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -238,6 +238,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
@@ -856,6 +857,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 da0135b..0b6e70e 100644
--- a/gdata/services/calendar/gdata-calendar-calendar.c
+++ b/gdata/services/calendar/gdata-calendar-calendar.c
@@ -392,12 +392,32 @@ parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GEr
            gdata_parser_boolean_from_json_member (reader, "selected", P_DEFAULT, &self->priv->is_selected, 
&success, error) ||
            gdata_parser_string_from_json_member (reader, "summary", P_DEFAULT, &summary, &success, error)) {
                if (success) {
-                       gdata_entry_set_title (GDATA_ENTRY (parsable), summary);
+                       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), "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);
+               }
+
+               g_free (summary);
+               return GDATA_PARSABLE_CLASS (gdata_calendar_calendar_parent_class)->parse_json (parsable, 
reader, user_data, error);
        } else {
                g_free (summary);
                return GDATA_PARSABLE_CLASS (gdata_calendar_calendar_parent_class)->parse_json (parsable, 
reader, user_data, error);
diff --git a/gdata/services/calendar/gdata-calendar-event.c b/gdata/services/calendar/gdata-calendar-event.c
index 069bf33..902414b 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 <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
@@ -84,7 +84,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 +98,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,12 +108,12 @@ 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;
@@ -155,11 +154,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:
@@ -438,7 +437,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 +511,557 @@ 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;
+}
+
+static gboolean
+parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
 {
        gboolean success;
+       gchar *summary = NULL;
+       gint64 start_time = -1, end_time = -1;
+       gboolean seen_start = FALSE, seen_end = FALSE, added_when = FALSE;
+       gboolean start_is_date = FALSE, end_is_date = FALSE;
        GDataCalendarEvent *self = GDATA_CALENDAR_EVENT (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) {
+g_message ("parsing %s", json_reader_get_member_name (reader));
+       /* TODO:
+  "htmlLink": string,
+  "created": datetime,
+  "description": string,
+  "location": string,
+  "colorId": string,
+  "creator": {
+    "id": string,
+    "email": string,
+    "displayName": string,
+    "self": boolean
+  },
+  "organizer": {
+    "id": string,
+    "email": string,
+    "displayName": string,
+    "self": boolean
+  },
+  "endTimeUnspecified": boolean,
+  "originalStartTime": {
+    "date": date,
+    "dateTime": datetime,
+    "timeZone": string
+  },
+  "attendees": [
+    {
+      "id": string,
+      "email": string,
+      "displayName": string,
+      "organizer": boolean,
+      "self": boolean,
+      "resource": boolean,
+      "optional": boolean,
+      "responseStatus": string,
+      "comment": string,
+      "additionalGuests": integer
+    }
+  ],
+  "attendeesOmitted": boolean,
+  "extendedProperties": {
+    "private": {
+      (key): string
+    },
+    "shared": {
+      (key): string
+    }
+  },
+  "hangoutLink": string,
+  "gadget": {
+    "type": string,
+    "title": string,
+    "link": string,
+    "iconLink": string,
+    "width": integer,
+    "height": integer,
+    "display": string,
+    "preferences": {
+      (key): string
+    }
+  },
+  "privateCopy": boolean,
+  "locked": boolean,
+  "reminders": {
+    "useDefault": boolean,
+    "overrides": [
+      {
+        "method": string,
+        "minutes": integer
+      }
+    ]
+  },
+  "source": {
+    "url": string,
+    "title": string
+  }
+
+
+who
+where
+originalEvent
+
+TODO: check visibility, transparency values match
+TODO: check status/eventStatus values match
+TODO: set original_event_uri iff original_event_id is set
+        */
+
+       if (g_strcmp0 (json_reader_get_member_name (reader), "start") == 0) {
+               seen_start = TRUE;
+       } else if (g_strcmp0 (json_reader_get_member_name (reader), "end") == 0) {
+               seen_end = TRUE;
+       }
+
+       if (gdata_parser_string_from_json_member (reader, "recurringEventId", P_DEFAULT, 
&self->priv->original_event_id, &success, error) ||
+           gdata_parser_string_from_json_member (reader, "status", P_DEFAULT, &self->priv->status, &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) ||
+           gdata_parser_string_from_json_member (reader, "transparency", P_DEFAULT, 
&self->priv->transparency, &success, error) ||
+           gdata_parser_string_from_json_member (reader, "visibility", P_DEFAULT, &self->priv->visibility, 
&success, error) ||
+           gdata_parser_string_from_json_member (reader, "summary", P_DEFAULT, &summary, &success, error) ||
+           date_object_from_json (reader, "start", P_DEFAULT, &start_time, &start_is_date, &success, error) 
||
+           date_object_from_json (reader, "end", P_DEFAULT, &end_time, &end_is_date, &success, error)) {
+               if (success) {
+                       if (summary != NULL) {
+                               gdata_entry_set_title (GDATA_ENTRY (parsable), summary);
+                       }
+
+                       if (self->priv->edited != -1) {
+                               _gdata_entry_set_updated (GDATA_ENTRY (parsable),
+                                                         self->priv->edited);
+                       }
+
+                       if (seen_start && seen_end && !added_when) {
+                               GDataGDWhen *when;
+
+                               when = gdata_gd_when_new (start_time, end_time, start_is_date || end_is_date);
+                               self->priv->times = g_list_prepend (self->priv->times, when);  /* transfer 
ownership */
+                               added_when = TRUE;
+                       }
+               }
+
+               g_free (summary);
+
                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), "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 (!json_reader_is_array (reader)) {
+                       /* TODO: error */
+                       return FALSE;
+               } else if (self->priv->recurrence != NULL) {
+                       /* TODO: error */
+                       return FALSE;
                }
-       } 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)
-                               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)
-                               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);
+
+               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) {
+                               return gdata_parser_error_from_json_error (reader, child_error, error);
+                       }
+
+                       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), "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);
+               }
+
+               g_free (summary);
+               return GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->parse_json (parsable, 
reader, user_data, error);
        } else {
-               return GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->parse_xml (parsable, doc, 
node, user_data, error);
+               g_free (summary);
+               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;
+       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);
-}
+/* TODO:
+  "htmlLink": string,
+  "created": datetime,
+  "description": string,
+  "location": string,
+  "colorId": string,
+  "creator": {
+    "id": string,
+    "email": string,
+    "displayName": string,
+    "self": boolean
+  },
+  "organizer": {
+    "id": string,
+    "email": string,
+    "displayName": string,
+    "self": boolean
+  },
+  "originalStartTime": {
+    "date": date,
+    "dateTime": datetime,
+    "timeZone": string
+  },
+  "attendees": [
+    {
+      "id": string,
+      "email": string,
+      "displayName": string,
+      "organizer": boolean,
+      "self": boolean,
+      "resource": boolean,
+      "optional": boolean,
+      "responseStatus": string,
+      "comment": string,
+      "additionalGuests": integer
+    }
+  ],
+  "attendeesOmitted": boolean,
+  "extendedProperties": {
+    "private": {
+      (key): string
+    },
+    "shared": {
+      (key): string
+    }
+  },
+  "hangoutLink": string,
+  "gadget": {
+    "type": string,
+    "title": string,
+    "link": string,
+    "iconLink": string,
+    "width": integer,
+    "height": integer,
+    "display": string,
+    "preferences": {
+      (key): string
+    }
+  },
+  "privateCopy": boolean,
+  "locked": boolean,
+  "reminders": {
+    "useDefault": boolean,
+    "overrides": [
+      {
+        "method": string,
+        "minutes": integer
+      }
+    ]
+  },
+  "source": {
+    "url": string,
+    "title": string
+  }
+
+
+       GList *people;
+       GList *places;
+*/
+
+
+
+       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);
+       }
 
-static void
-get_xml (GDataParsable *parsable, GString *xml_string)
-{
-       GDataCalendarEventPrivate *priv = GDATA_CALENDAR_EVENT (parsable)->priv;
+       json_builder_set_member_name (builder, "kind");
+       json_builder_add_string_value (builder, "calendar#event");
 
-       /* Chain up to the parent class */
-       GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->get_xml (parsable, xml_string);
+       /* 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);
+       }
+
+       /* Add all the calendar-specific JSON */
+       json_builder_set_member_name (builder, "anyoneCanAddSelf");
+       json_builder_add_boolean_value (builder, priv->anyone_can_add_self);
+
+       json_builder_set_member_name (builder, "guestsCanInviteOthers");
+       json_builder_add_boolean_value (builder, priv->guests_can_invite_others);
 
-       /* Add all the Calendar-specific XML */
+       json_builder_set_member_name (builder, "guestsCanModify");
+       json_builder_add_boolean_value (builder, priv->guests_can_modify);
 
-       /* TODO: gd:comments? */
+       json_builder_set_member_name (builder, "guestsCanSeeOtherGuests");
+       json_builder_add_boolean_value (builder, priv->guests_can_see_guests);
 
-       if (priv->status != NULL)
-               gdata_parser_string_append_escaped (xml_string, "<gd:eventStatus value='", priv->status, 
"'/>");
+       if (priv->transparency != NULL) {
+               json_builder_set_member_name (builder, "transparency");
+               json_builder_add_string_value (builder, priv->transparency);
+       }
 
-       if (priv->visibility != NULL)
-               gdata_parser_string_append_escaped (xml_string, "<gd:visibility value='", priv->visibility, 
"'/>");
+       if (priv->visibility != NULL) {
+               json_builder_set_member_name (builder, "visibility");
+               json_builder_add_string_value (builder, priv->visibility);
+       }
 
-       if (priv->transparency != NULL)
-               gdata_parser_string_append_escaped (xml_string, "<gd:transparency value='", 
priv->transparency, "'/>");
+       if (priv->uid != NULL) {
+               json_builder_set_member_name (builder, "iCalUID");
+               json_builder_add_string_value (builder, priv->uid);
+       }
 
-       if (priv->uid != NULL)
-               gdata_parser_string_append_escaped (xml_string, "<gCal:uid value='", priv->uid, "'/>");
+       if (priv->sequence > 0) {
+               json_builder_set_member_name (builder, "sequence");
+               json_builder_add_int_value (builder, priv->sequence);
+       }
 
-       if (priv->sequence != 0)
-               g_string_append_printf (xml_string, "<gCal:sequence value='%u'/>", priv->sequence);
+       if (priv->status != NULL) {
+               json_builder_set_member_name (builder, "status");
+               json_builder_add_string_value (builder, priv->status);
+       }
 
-       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->recurrence != NULL) {
+               gchar **parts;
+               guint i;
 
-       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'/>");
+               json_builder_set_member_name (builder, "recurrence");
+               json_builder_begin_array (builder);
 
-       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'/>");
+               parts = g_strsplit (priv->recurrence, "\n", -1);
 
-       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'/>");
+               for (i = 0; parts[i] != NULL; i++) {
+                       json_builder_add_string_value (builder, parts[i]);
+               }
 
-       if (priv->recurrence != NULL)
-               gdata_parser_string_append_escaped (xml_string, "<gd:recurrence>", priv->recurrence, 
"</gd:recurrence>");
+               g_strfreev (parts);
 
-       get_child_xml (priv->times, xml_string);
-       get_child_xml (priv->people, xml_string);
-       get_child_xml (priv->places, xml_string);
+               json_builder_end_array (builder);
+       }
 
-       /* TODO:
-        * - Finish supporting all tags
-        * - Check all tags here are valid for insertions and updates
-        */
+       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.");
+               }
+       }
 }
 
-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 +1246,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-service.c 
b/gdata/services/calendar/gdata-calendar-service.c
index a141498..4f5b0fc 100644
--- a/gdata/services/calendar/gdata-calendar-service.c
+++ b/gdata/services/calendar/gdata-calendar-service.c
@@ -16,7 +16,6 @@
  * You should have received a copy of the GNU Lesser General Public
  * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
  *
- * TODO: parse_error_response
  * TODO: ACLs, batchable
  * TODO: update links to references; proofread documentation
  * TODO: add new API to avoid naked calls to gdata_service_insert_entry() (for example)
@@ -221,6 +220,14 @@
 
 /* Standards reference here: http://code.google.com/apis/calendar/docs/2.0/reference.html */
 
+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/";)
@@ -231,6 +238,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;
 }
 
@@ -240,6 +248,210 @@ 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) {
+                               /* 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)
 {
@@ -479,6 +691,22 @@ gdata_calendar_service_query_own_calendars_async (GDataCalendarService *self, GD
        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);
+}
+
 /**
  * gdata_calendar_service_query_events:
  * @self: a #GDataCalendarService
@@ -516,12 +744,8 @@ gdata_calendar_service_query_events (GDataCalendarService *self, GDataCalendarCa
                return NULL;
        }
 
-       /* Execute the query. TODO: escaping? */
-       request_uri = g_strconcat (_gdata_service_get_scheme (),
-                                  "://www.googleapis.com/calendar/v3/calendars/",
-                                  gdata_entry_get_id (GDATA_ENTRY (calendar)),
-                                  "/events",
-                                  NULL);
+       /* Execute the query. */
+       request_uri = build_events_uri (calendar);
        feed = gdata_service_query (GDATA_SERVICE (self),
                                    get_calendar_authorization_domain (),
                                    request_uri, query,
@@ -582,12 +806,8 @@ gdata_calendar_service_query_events_async (GDataCalendarService *self, GDataCale
                return;
        }
 
-       /* Execute the query */
-       request_uri = g_strconcat (_gdata_service_get_scheme (),
-                                  "://www.googleapis.com/calendar/v3/calendars/",
-                                  gdata_entry_get_id (GDATA_ENTRY (calendar)),
-                                  "/events",
-                                  NULL);
+       /* Execute the query. */
+       request_uri = build_events_uri (calendar);
        gdata_service_query_async (GDATA_SERVICE (self),
                                   get_calendar_authorization_domain (),
                                   request_uri, query,
@@ -610,6 +830,7 @@ gdata_calendar_service_query_events_async (GDataCalendarService *self, GDataCale
  * For more details, see gdata_service_insert_entry().
  *
  * Return value: (transfer full): an updated #GDataCalendarEvent, or %NULL; unref with g_object_unref()
+ * TODO: Deprecate
  *
  * Since: 0.2.0
  **/
@@ -619,18 +840,13 @@ gdata_calendar_service_insert_event (GDataCalendarService *self, GDataCalendarEv
        /* TODO: How do we choose which calendar? */
        gchar *uri;
        GDataEntry *entry;
-       const gchar *calendar_id = "default";
 
        g_return_val_if_fail (GDATA_IS_CALENDAR_SERVICE (self), 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 = g_strconcat (_gdata_service_get_scheme (),
-                          "://www.googleapis.com/calendar/v3/calendars/",
-                          calendar_id,
-                          "/events",
-                          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);
 
@@ -638,6 +854,50 @@ 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);
+
+       return GDATA_CALENDAR_EVENT (entry);
+}
+
+/**
  * gdata_calendar_service_insert_event_async:
  * @self: a #GDataCalendarService
  * @event: the #GDataCalendarEvent to insert
@@ -653,6 +913,7 @@ gdata_calendar_service_insert_event (GDataCalendarService *self, GDataCalendarEv
  *
  * 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.
+ * TODO: deprecate
  *
  * Since: 0.8.0
  **/
@@ -661,18 +922,58 @@ gdata_calendar_service_insert_event_async (GDataCalendarService *self, GDataCale
                                            GAsyncReadyCallback callback, gpointer user_data)
 {
        gchar *uri;
-       const gchar *calendar_id = "default";
 
        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 = g_strconcat (_gdata_service_get_scheme (),
-                          "://www.googleapis.com/calendar/v3/calendars/",
-                          calendar_id,
-                          "/events",
-                          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 8177491..ffc63ac 100644
--- a/gdata/services/calendar/gdata-calendar-service.h
+++ b/gdata/services/calendar/gdata-calendar-service.h
@@ -100,6 +100,20 @@ GDataCalendarEvent *gdata_calendar_service_insert_event (GDataCalendarService *s
 void gdata_calendar_service_insert_event_async (GDataCalendarService *self, GDataCalendarEvent *event, 
GCancellable *cancellable,
                                                 GAsyncReadyCallback callback, gpointer user_data);
 
+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
 
 #endif /* !GDATA_CALENDAR_SERVICE_H */
diff --git a/gdata/tests/calendar.c b/gdata/tests/calendar.c
index 85b6da3..f808dce 100644
--- a/gdata/tests/calendar.c
+++ b/gdata/tests/calendar.c
@@ -322,6 +322,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);
@@ -331,19 +333,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);
 
@@ -501,7 +521,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);
@@ -543,7 +564,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,
@@ -825,6 +848,82 @@ test_event_escaping (void)
        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 */
+
+       self_link = gdata_entry_look_up_link (entry, GDATA_LINK_SELF);
+       g_assert (GDATA_IS_LINK (self_link));
+       g_assert_cmpstr (gdata_link_get_uri (self_link), ==,
+                        "https://www.googleapis.com/calendar/v3/calendars/";
+                        "hsfgtc50u68vdai81t6634u7lg");
+       g_assert_cmpstr (gdata_link_get_relation_type (self_link), ==,
+                        GDATA_LINK_SELF);
+       g_assert_cmpstr (gdata_link_get_content_type (self_link), ==, NULL);
+       g_assert_cmpstr (gdata_link_get_language (self_link), ==, NULL);
+       g_assert_cmpstr (gdata_link_get_title (self_link), ==, NULL);
+       g_assert_cmpint (gdata_link_get_length (self_link), ==, -1);
+
+       g_object_unref (event);
+}
+
 static void
 test_access_rule_properties (void)
 {
@@ -1561,6 +1660,8 @@ TODO
        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/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);
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]