[evolution-ews/wip/mcrha/office365: 37/50] Recognize user calendars and add them as ESource-s




commit 1cec4ab8d896d0428fb6fb8b9def4cc61d6e2dc9
Author: Milan Crha <mcrha redhat com>
Date:   Wed Jul 15 17:38:02 2020 +0200

    Recognize user calendars and add them as ESource-s

 src/Office365/addressbook/e-book-backend-o365.c |   2 +-
 src/Office365/camel/camel-o365-store.c          |   2 +-
 src/Office365/camel/camel-o365-transport.c      |   2 +-
 src/Office365/common/e-o365-connection.c        | 587 ++++++++++++++++++++++--
 src/Office365/common/e-o365-connection.h        |  93 +++-
 src/Office365/common/e-o365-json-utils.c        | 302 ++++++++++++
 src/Office365/common/e-o365-json-utils.h        |  65 +++
 src/Office365/common/e-source-o365-folder.c     |  78 +++-
 src/Office365/common/e-source-o365-folder.h     |   7 +
 src/Office365/registry/e-o365-backend.c         | 161 ++++++-
 10 files changed, 1233 insertions(+), 66 deletions(-)
---
diff --git a/src/Office365/addressbook/e-book-backend-o365.c b/src/Office365/addressbook/e-book-backend-o365.c
index 16ba8ddb..1b127c13 100644
--- a/src/Office365/addressbook/e-book-backend-o365.c
+++ b/src/Office365/addressbook/e-book-backend-o365.c
@@ -1423,7 +1423,7 @@ ebb_o365_connect_sync (EBookMetaBackend *meta_backend,
                if (folder_id) {
                        cnc = e_o365_connection_new_for_backend (backend, registry, source, o365_settings);
 
-                       *out_auth_result = e_o365_connection_authenticate_sync (cnc, NULL, 
E_O365_FOLDER_KIND_CONTACTS, folder_id,
+                       *out_auth_result = e_o365_connection_authenticate_sync (cnc, NULL, 
E_O365_FOLDER_KIND_CONTACTS, NULL, folder_id,
                                out_certificate_pem, out_certificate_errors, cancellable, error);
 
                        if (*out_auth_result == E_SOURCE_AUTHENTICATION_ACCEPTED) {
diff --git a/src/Office365/camel/camel-o365-store.c b/src/Office365/camel/camel-o365-store.c
index 4142b23a..acae3c8b 100644
--- a/src/Office365/camel/camel-o365-store.c
+++ b/src/Office365/camel/camel-o365-store.c
@@ -597,7 +597,7 @@ o365_store_authenticate_sync (CamelService *service,
        if (!cnc)
                return CAMEL_AUTHENTICATION_ERROR;
 
-       switch (e_o365_connection_authenticate_sync (cnc, NULL, E_O365_FOLDER_KIND_MAIL, NULL, NULL, NULL, 
cancellable, error)) {
+       switch (e_o365_connection_authenticate_sync (cnc, NULL, E_O365_FOLDER_KIND_MAIL, NULL, NULL, NULL, 
NULL, cancellable, error)) {
        case E_SOURCE_AUTHENTICATION_ERROR:
        case E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED:
        default:
diff --git a/src/Office365/camel/camel-o365-transport.c b/src/Office365/camel/camel-o365-transport.c
index 7149f975..5be5359f 100644
--- a/src/Office365/camel/camel-o365-transport.c
+++ b/src/Office365/camel/camel-o365-transport.c
@@ -238,7 +238,7 @@ o365_transport_authenticate_sync (CamelService *service,
        if (!cnc)
                return CAMEL_AUTHENTICATION_ERROR;
 
-       switch (e_o365_connection_authenticate_sync (cnc, NULL, E_O365_FOLDER_KIND_MAIL, NULL, NULL, NULL, 
cancellable, error)) {
+       switch (e_o365_connection_authenticate_sync (cnc, NULL, E_O365_FOLDER_KIND_MAIL, NULL, NULL, NULL, 
NULL, cancellable, error)) {
        case E_SOURCE_AUTHENTICATION_ERROR:
        case E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED:
        default:
diff --git a/src/Office365/common/e-o365-connection.c b/src/Office365/common/e-o365-connection.c
index 52d6dddd..9c9570e1 100644
--- a/src/Office365/common/e-o365-connection.c
+++ b/src/Office365/common/e-o365-connection.c
@@ -1557,6 +1557,7 @@ ESourceAuthenticationResult
 e_o365_connection_authenticate_sync (EO365Connection *cnc,
                                     const gchar *user_override,
                                     EO365FolderKind kind,
+                                    const gchar *group_id,
                                     const gchar *folder_id,
                                     gchar **out_certificate_pem,
                                     GTlsCertificateFlags *out_certificate_errors,
@@ -1564,57 +1565,37 @@ e_o365_connection_authenticate_sync (EO365Connection *cnc,
                                     GError **error)
 {
        ESourceAuthenticationResult result = E_SOURCE_AUTHENTICATION_ERROR;
-       SoupMessage *message;
        JsonObject *object = NULL;
-       gchar *uri;
-       const gchar *resource = NULL;
-       gboolean success;
+       gboolean success = FALSE;
        GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), result);
 
-       /* Just pick an inexpensive operation */
        switch (kind) {
+       default:
+               g_warn_if_reached ();
+               /* Falls through */
        case E_O365_FOLDER_KIND_UNKNOWN:
        case E_O365_FOLDER_KIND_MAIL:
-               resource = "mailFolders";
-
                if (!folder_id || !*folder_id)
                        folder_id = "inbox";
+
+               success = e_o365_connection_get_mail_folder_sync (cnc, user_override, folder_id, 
"displayName", &object, cancellable, &local_error);
                break;
        case E_O365_FOLDER_KIND_CONTACTS:
-               resource = "contactFolders";
-
                if (!folder_id || !*folder_id)
                        folder_id = "contacts";
-               break;
-       default:
-               g_warn_if_reached ();
 
-               resource = "mailFolders";
-               folder_id = "inbox";
+               success = e_o365_connection_get_contacts_folder_sync (cnc, user_override, folder_id, 
"displayName", &object, cancellable, &local_error);
                break;
-       }
-
-       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
-               resource,
-               folder_id,
-               NULL,
-               "$select", "displayName",
-               NULL);
-
-       message = o365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
-
-       if (!message) {
-               g_free (uri);
+       case E_O365_FOLDER_KIND_CALENDAR:
+               if (folder_id && !*folder_id)
+                       folder_id = NULL;
 
-               return FALSE;
+               success = e_o365_connection_get_calendar_folder_sync (cnc, user_override, group_id, 
folder_id, "name", &object, cancellable, error);
+               break;
        }
 
-       g_free (uri);
-
-       success = o365_connection_send_request_sync (cnc, message, e_o365_read_json_object_response_cb, NULL, 
&object, cancellable, &local_error);
-
        if (success) {
                result = E_SOURCE_AUTHENTICATION_ACCEPTED;
        } else {
@@ -1659,7 +1640,6 @@ e_o365_connection_authenticate_sync (EO365Connection *cnc,
        if (object)
                json_object_unref (object);
 
-       g_clear_object (&message);
        g_clear_error (&local_error);
 
        return result;
@@ -2382,6 +2362,48 @@ e_o365_connection_get_folders_delta_sync (EO365Connection *cnc,
        return success;
 }
 
+/* https://docs.microsoft.com/en-us/graph/api/mailfolder-get?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_get_mail_folder_sync (EO365Connection *cnc,
+                                       const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                       const gchar *folder_id, /* nullable - then the 'inbox' is used */
+                                       const gchar *select, /* nullable - properties to select */
+                                       EO365MailFolder **out_folder,
+                                       GCancellable *cancellable,
+                                       GError **error)
+{
+       SoupMessage *message;
+       gchar *uri;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (out_folder != NULL, FALSE);
+
+       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+               "mailFolders",
+               folder_id ? folder_id : "inbox",
+               NULL,
+               "$select", select,
+               NULL);
+
+       message = o365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = o365_connection_send_request_sync (cnc, message, e_o365_read_json_object_response_cb, NULL, 
out_folder, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
 /* https://docs.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http
    https://docs.microsoft.com/en-us/graph/api/mailfolder-post-childfolders?view=graph-rest-1.0&tabs=http */
 
@@ -3244,9 +3266,13 @@ e_o365_connection_send_mail_sync (EO365Connection *cnc,
        return success;
 }
 
+/* https://docs.microsoft.com/en-us/graph/api/contactfolder-get?view=graph-rest-1.0&tabs=http */
+
 gboolean
 e_o365_connection_get_contacts_folder_sync (EO365Connection *cnc,
                                            const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                           const gchar *folder_id, /* nullable - then the default 'contacts' 
folder is returned */
+                                           const gchar *select, /* nullable - properties to select */
                                            EO365Folder **out_folder,
                                            GCancellable *cancellable,
                                            GError **error)
@@ -3260,7 +3286,7 @@ e_o365_connection_get_contacts_folder_sync (EO365Connection *cnc,
 
        uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
                "contactFolders",
-               "contacts",
+               folder_id ? folder_id : "contacts",
                NULL,
                NULL);
 
@@ -3550,3 +3576,496 @@ e_o365_connection_delete_contact_sync (EO365Connection *cnc,
 
        return success;
 }
+
+/* https://docs.microsoft.com/en-us/graph/api/user-list-calendargroups?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_list_calendar_groups_sync (EO365Connection *cnc,
+                                            const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                            GSList **out_groups, /* EO365CalendarGroup * - the returned 
calendarGroup objects */
+                                            GCancellable *cancellable,
+                                            GError **error)
+{
+       EO365ResponseData rd;
+       SoupMessage *message;
+       gchar *uri;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (out_groups != NULL, FALSE);
+
+       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+               "calendarGroups", NULL, NULL, NULL);
+
+       message = o365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       memset (&rd, 0, sizeof (EO365ResponseData));
+
+       rd.out_items = out_groups;
+
+       success = o365_connection_send_request_sync (cnc, message, e_o365_read_valued_response_cb, NULL, &rd, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/user-post-calendargroups?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_create_calendar_group_sync (EO365Connection *cnc,
+                                             const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                             const gchar *name,
+                                             EO365CalendarGroup **out_created_group,
+                                             GCancellable *cancellable,
+                                             GError **error)
+{
+       SoupMessage *message;
+       JsonBuilder *builder;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+       g_return_val_if_fail (out_created_group != NULL, FALSE);
+
+       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+               "calendarGroups", NULL, NULL, NULL);
+
+       message = o365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       builder = json_builder_new_immutable ();
+
+       e_o365_json_begin_object_member (builder, NULL);
+       e_o365_json_add_string_member (builder, "name", name);
+       e_o365_json_end_object_member (builder);
+
+       e_o365_connection_set_json_body (message, builder);
+
+       g_object_unref (builder);
+
+       success = o365_connection_send_request_sync (cnc, message, e_o365_read_json_object_response_cb, NULL, 
out_created_group, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendargroup-get?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_get_calendar_group_sync (EO365Connection *cnc,
+                                          const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                          const gchar *group_id,
+                                          EO365CalendarGroup **out_group,
+                                          GCancellable *cancellable,
+                                          GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (group_id != NULL, FALSE);
+       g_return_val_if_fail (out_group != NULL, FALSE);
+
+       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+               "calendarGroups",
+               group_id,
+               NULL,
+               NULL);
+
+       message = o365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = o365_connection_send_request_sync (cnc, message, e_o365_read_json_object_response_cb, NULL, 
out_group, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendargroup-update?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_update_calendar_group_sync (EO365Connection *cnc,
+                                             const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                             const gchar *group_id,
+                                             const gchar *name,
+                                             GCancellable *cancellable,
+                                             GError **error)
+{
+       SoupMessage *message;
+       JsonBuilder *builder;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (group_id != NULL, FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+               "calendarGroups",
+               group_id,
+               NULL,
+               NULL);
+
+       message = o365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       builder = json_builder_new_immutable ();
+
+       e_o365_json_begin_object_member (builder, NULL);
+       e_o365_json_add_string_member (builder, "name", name);
+       e_o365_json_end_object_member (builder);
+
+       e_o365_connection_set_json_body (message, builder);
+
+       g_object_unref (builder);
+
+       success = o365_connection_send_request_sync (cnc, message, NULL, e_o365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendargroup-delete?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_delete_calendar_group_sync (EO365Connection *cnc,
+                                             const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                             const gchar *group_id,
+                                             GCancellable *cancellable,
+                                             GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (group_id != NULL, FALSE);
+
+       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+               "calendarGroups", group_id, NULL, NULL);
+
+       message = o365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = o365_connection_send_request_sync (cnc, message, NULL, e_o365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/calendar?view=graph-rest-1.0 */
+
+gboolean
+e_o365_connection_list_calendars_sync (EO365Connection *cnc,
+                                      const gchar *user_override, /* for which user, NULL to use the account 
user */
+                                      const gchar *group_id, /* nullable, calendar group id for group 
calendars */
+                                      const gchar *select, /* properties to select, nullable */
+                                      GSList **out_calendars, /* EO365Calendar * - the returned calendar 
objects */
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+       EO365ResponseData rd;
+       SoupMessage *message;
+       gchar *uri;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (out_calendars != NULL, FALSE);
+
+       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+               group_id ? "calendarGroups" : "calendars",
+               group_id,
+               group_id ? "calendars" : NULL,
+               "$select", select,
+               NULL);
+
+       message = o365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       memset (&rd, 0, sizeof (EO365ResponseData));
+
+       rd.out_items = out_calendars;
+
+       success = o365_connection_send_request_sync (cnc, message, e_o365_read_valued_response_cb, NULL, &rd, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendargroup-post-calendars?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_create_calendar_sync (EO365Connection *cnc,
+                                       const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                       const gchar *group_id, /* nullable, then the default group is used */
+                                       JsonBuilder *calendar,
+                                       EO365Calendar **out_created_calendar,
+                                       GCancellable *cancellable,
+                                       GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (calendar != NULL, FALSE);
+       g_return_val_if_fail (out_created_calendar != NULL, FALSE);
+
+       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+               group_id ? "calendarGroups" : "calendarGroup",
+               group_id,
+               "calendars",
+               NULL);
+
+       message = o365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       e_o365_connection_set_json_body (message, calendar);
+
+       success = o365_connection_send_request_sync (cnc, message, e_o365_read_json_object_response_cb, NULL, 
out_created_calendar, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendar-get?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_get_calendar_folder_sync (EO365Connection *cnc,
+                                           const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                           const gchar *group_id, /* nullable - then the default group is 
used */
+                                           const gchar *calendar_id, /* nullable - then the default calendar 
is used */
+                                           const gchar *select, /* nullable - properties to select */
+                                           EO365Calendar **out_calendar,
+                                           GCancellable *cancellable,
+                                           GError **error)
+{
+       SoupMessage *message;
+       gchar *uri;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (out_calendar != NULL, FALSE);
+
+       if (group_id && calendar_id) {
+               uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+                       "calendarGroups",
+                       group_id,
+                       "calendars",
+                       "", calendar_id,
+                       "$select", select,
+                       NULL);
+       } else if (group_id) {
+               uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, "groups",
+                       group_id,
+                       "calendar",
+                       NULL,
+                       "$select", select,
+                       NULL);
+       } else if (calendar_id) {
+               uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+                       "calendars",
+                       calendar_id,
+                       NULL,
+                       "$select", select,
+                       NULL);
+       } else {
+               uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+                       "calendar",
+                       NULL,
+                       NULL,
+                       "$select", select,
+                       NULL);
+       }
+
+       message = o365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = o365_connection_send_request_sync (cnc, message, e_o365_read_json_object_response_cb, NULL, 
out_calendar, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendar-update?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_update_calendar_sync (EO365Connection *cnc,
+                                       const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                       const gchar *group_id, /* nullable - then the default group is used */
+                                       const gchar *calendar_id,
+                                       const gchar *name, /* nullable - to keep the existing name */
+                                       EO365CalendarColorType color,
+                                       GCancellable *cancellable,
+                                       GError **error)
+{
+       SoupMessage *message;
+       JsonBuilder *builder;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (calendar_id != NULL, FALSE);
+
+       /* Nothing to change */
+       if (!name && (color == E_O365_CALENDAR_COLOR_NOT_SET || color == E_O365_CALENDAR_COLOR_UNKNOWN))
+               return TRUE;
+
+       if (group_id) {
+               uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+                       "calendarGroups",
+                       group_id,
+                       "calendars",
+                       "", calendar_id,
+                       NULL);
+       } else {
+               uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+                       "calendars",
+                       calendar_id,
+                       NULL,
+                       NULL);
+       }
+
+       message = o365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       builder = json_builder_new_immutable ();
+
+       e_o365_json_begin_object_member (builder, NULL);
+       e_o365_calendar_add_name (builder, name);
+       e_o365_calendar_add_color (builder, color);
+       e_o365_json_end_object_member (builder);
+
+       e_o365_connection_set_json_body (message, builder);
+
+       g_object_unref (builder);
+
+       success = o365_connection_send_request_sync (cnc, message, NULL, e_o365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendar-delete?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_delete_calendar_sync (EO365Connection *cnc,
+                                       const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                       const gchar *group_id, /* nullable - then the default group is used */
+                                       const gchar *calendar_id,
+                                       GCancellable *cancellable,
+                                       GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (calendar_id != NULL, FALSE);
+
+       if (group_id) {
+               uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+                       "calendarGroups",
+                       group_id,
+                       "calendars",
+                       "", calendar_id,
+                       NULL);
+       } else {
+               uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+                       "calendars",
+                       calendar_id,
+                       NULL,
+                       NULL);
+       }
+
+       message = o365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       success = o365_connection_send_request_sync (cnc, message, NULL, e_o365_read_no_response_cb, NULL, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
diff --git a/src/Office365/common/e-o365-connection.h b/src/Office365/common/e-o365-connection.h
index 433f2828..db41ae97 100644
--- a/src/Office365/common/e-o365-connection.h
+++ b/src/Office365/common/e-o365-connection.h
@@ -131,6 +131,7 @@ ESourceAuthenticationResult
                                                (EO365Connection *cnc,
                                                 const gchar *user_override,
                                                 EO365FolderKind kind,
+                                                const gchar *group_id,
                                                 const gchar *folder_id,
                                                 gchar **out_certificate_pem,
                                                 GTlsCertificateFlags *out_certificate_errors,
@@ -177,7 +178,7 @@ gboolean    e_o365_connection_list_mail_folders_sync
                                                (EO365Connection *cnc,
                                                 const gchar *user_override, /* for which user, NULL to use 
the account user */
                                                 const gchar *from_path, /* path for the folder to read, NULL 
for top user folder */
-                                                const gchar *select, /* properties to select, nullable */
+                                                const gchar *select, /* nullable - properties to select */
                                                 GSList **out_folders, /* EO365MailFolder * - the returned 
mailFolder objects */
                                                 GCancellable *cancellable,
                                                 GError **error);
@@ -185,7 +186,7 @@ gboolean    e_o365_connection_get_folders_delta_sync
                                                (EO365Connection *cnc,
                                                 const gchar *user_override, /* for which user, NULL to use 
the account user */
                                                 EO365FolderKind kind,
-                                                const gchar *select, /* properties to select, nullable */
+                                                const gchar *select, /* nullable - properties to select */
                                                 const gchar *delta_link, /* previous delta link */
                                                 guint max_page_size, /* 0 for default by the server */
                                                 EO365ConnectionJsonFunc func, /* function to call with each 
result set */
@@ -193,6 +194,14 @@ gboolean   e_o365_connection_get_folders_delta_sync
                                                 gchar **out_delta_link,
                                                 GCancellable *cancellable,
                                                 GError **error);
+gboolean       e_o365_connection_get_mail_folder_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *folder_id, /* nullable - then the 'inbox' is 
used */
+                                                const gchar *select, /* nullable - properties to select */
+                                                EO365MailFolder **out_folder,
+                                                GCancellable *cancellable,
+                                                GError **error);
 gboolean       e_o365_connection_create_mail_folder_sync
                                                (EO365Connection *cnc,
                                                 const gchar *user_override, /* for which user, NULL to use 
the account user */
@@ -229,7 +238,7 @@ gboolean    e_o365_connection_get_objects_delta_sync
                                                 const gchar *user_override, /* for which user, NULL to use 
the account user */
                                                 EO365FolderKind kind,
                                                 const gchar *folder_id, /* folder ID to get delta messages 
in */
-                                                const gchar *select, /* properties to select, nullable */
+                                                const gchar *select, /* nullable - properties to select */
                                                 const gchar *delta_link, /* previous delta link */
                                                 guint max_page_size, /* 0 for default by the server */
                                                 EO365ConnectionJsonFunc func, /* function to call with each 
result set */
@@ -306,6 +315,8 @@ gboolean    e_o365_connection_send_mail_sync
 gboolean       e_o365_connection_get_contacts_folder_sync
                                                (EO365Connection *cnc,
                                                 const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *folder_id, /* nullable - then the default 
'contacts' folder is returned */
+                                                const gchar *select, /* nullable - properties to select */
                                                 EO365Folder **out_folder,
                                                 GCancellable *cancellable,
                                                 GError **error);
@@ -322,7 +333,7 @@ gboolean    e_o365_connection_update_contact_photo_sync
                                                 const gchar *user_override, /* for which user, NULL to use 
the account user */
                                                 const gchar *folder_id,
                                                 const gchar *contact_id,
-                                                const GByteArray *jpeg_photo, /* nullable, to remove the 
photo */
+                                                const GByteArray *jpeg_photo, /* nullable - to remove the 
photo */
                                                 GCancellable *cancellable,
                                                 GError **error);
 gboolean       e_o365_connection_get_contact_sync
@@ -356,6 +367,80 @@ gboolean   e_o365_connection_delete_contact_sync
                                                 const gchar *contact_id,
                                                 GCancellable *cancellable,
                                                 GError **error);
+gboolean       e_o365_connection_list_calendar_groups_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                GSList **out_groups, /* EO365CalendarGroup * - the returned 
calendarGroup objects */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_create_calendar_group_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *name,
+                                                EO365CalendarGroup **out_created_group,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_get_calendar_group_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id,
+                                                EO365CalendarGroup **out_group,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_update_calendar_group_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id,
+                                                const gchar *name,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_delete_calendar_group_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_list_calendars_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable - calendar group for 
group calendars */
+                                                const gchar *select, /* nullable - properties to select */
+                                                GSList **out_calendars, /* EO365Calendar * - the returned 
calendar objects */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_create_calendar_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable - then the default group 
is used */
+                                                JsonBuilder *calendar,
+                                                EO365Calendar **out_created_calendar,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_get_calendar_folder_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable - then the default group 
is used */
+                                                const gchar *calendar_id, /* nullable - then the default 
calendar is used */
+                                                const gchar *select, /* nullable - properties to select */
+                                                EO365Calendar **out_calendar,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_update_calendar_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable - then the default group 
is used */
+                                                const gchar *calendar_id,
+                                                const gchar *name, /* nullable - to keep the existing name */
+                                                EO365CalendarColorType color,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_delete_calendar_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *group_id, /* nullable - then the default group 
is used */
+                                                const gchar *calendar_id,
+                                                GCancellable *cancellable,
+                                                GError **error);
 
 G_END_DECLS
 
diff --git a/src/Office365/common/e-o365-json-utils.c b/src/Office365/common/e-o365-json-utils.c
index 18b95bf2..0ec0e982 100644
--- a/src/Office365/common/e-o365-json-utils.c
+++ b/src/Office365/common/e-o365-json-utils.c
@@ -17,10 +17,84 @@
 
 #include "evolution-ews-config.h"
 
+#include <stdio.h>
 #include <json-glib/json-glib.h>
 
 #include "e-o365-json-utils.h"
 
+static struct _color_map {
+       const gchar *name;
+       const gchar *rgb;
+       EO365CalendarColorType value;
+} color_map[] = {
+       { "auto",       NULL,           E_O365_CALENDAR_COLOR_AUTO },
+       { "lightBlue",  "#0078d4",      E_O365_CALENDAR_COLOR_LIGHT_BLUE },
+       { "lightGreen", "#b67dfa",      E_O365_CALENDAR_COLOR_LIGHT_GREEN },
+       { "lightOrange","#25c4fe",      E_O365_CALENDAR_COLOR_LIGHT_ORANGE },
+       { "lightGray",  "#968681",      E_O365_CALENDAR_COLOR_LIGHT_GRAY },
+       { "lightYellow","#ffc699",      E_O365_CALENDAR_COLOR_LIGHT_YELLOW }, /* Navy in web UI */
+       { "lightTeal",  "#fc7c78",      E_O365_CALENDAR_COLOR_LIGHT_TEAL },
+       { "lightPink",  "#1cff73",      E_O365_CALENDAR_COLOR_LIGHT_PINK },
+       { "lightBrown", "#8bb256",      E_O365_CALENDAR_COLOR_LIGHT_BROWN }, /* Purple in web UI */
+       { "lightRed",   "#3af0e0",      E_O365_CALENDAR_COLOR_LIGHT_RED },
+       { "maxColor",   NULL,           E_O365_CALENDAR_COLOR_MAX_COLOR }
+};
+
+const gchar *
+e_o365_calendar_color_to_rgb (EO365CalendarColorType color)
+{
+       gint ii;
+
+       for (ii = 0; ii < G_N_ELEMENTS (color_map); ii++) {
+               if (color == color_map[ii].value)
+                       return color_map[ii].rgb;
+       }
+
+       return NULL;
+}
+
+EO365CalendarColorType
+e_o365_rgb_to_calendar_color (const gchar *rgb)
+{
+       EO365CalendarColorType res;
+       gint ii, rr, gg, bb;
+       gdouble distance, res_distance = -1.0;
+
+       if (!rgb || !*rgb)
+               return E_O365_CALENDAR_COLOR_NOT_SET;
+
+       for (ii = 0; ii < G_N_ELEMENTS (color_map); ii++) {
+               if (color_map[ii].rgb && g_ascii_strcasecmp (color_map[ii].rgb, rgb) == 0)
+                       return color_map[ii].value;
+       }
+
+       /* When exact match did not work, approximate to the closest */
+
+       if (sscanf (rgb, "#%02x%02x%02x", &rr, &gg, &bb) != 3)
+               return E_O365_CALENDAR_COLOR_UNKNOWN;
+
+       distance = (rr * rr) + (gg * gg) + (bb * bb);
+       res = E_O365_CALENDAR_COLOR_UNKNOWN;
+
+       for (ii = 0; ii < G_N_ELEMENTS (color_map); ii++) {
+               if (color_map[ii].rgb && sscanf (color_map[ii].rgb, "#%02x%02x%02x", &rr, &gg, &bb) == 3) {
+                       gdouble candidate_distance;
+
+                       candidate_distance = (rr * rr) + (gg * gg) + (bb * bb) - distance;
+
+                       if (candidate_distance < 0.0)
+                               candidate_distance *= -1.0;
+
+                       if (!ii || candidate_distance < res_distance) {
+                               res_distance = candidate_distance;
+                               res = color_map[ii].value;
+                       }
+               }
+       }
+
+       return res;
+}
+
 JsonArray *
 e_o365_json_get_array_member (JsonObject *object,
                              const gchar *member_name)
@@ -1866,3 +1940,231 @@ e_o365_contact_add_yomi_surname (JsonBuilder *builder,
 {
        e_o365_json_add_nonempty_or_null_string_member (builder, "yomiSurname", value);
 }
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/calendargroup?view=graph-rest-1.0 */
+
+const gchar *
+e_o365_calendar_group_get_id (EO365CalendarGroup *group)
+{
+       return e_o365_json_get_string_member (group, "id", NULL);
+}
+
+const gchar *
+e_o365_calendar_group_get_change_key (EO365CalendarGroup *group)
+{
+       return e_o365_json_get_string_member (group, "changeKey", NULL);
+}
+
+const gchar *
+e_o365_calendar_group_get_class_id (EO365CalendarGroup *group)
+{
+       return e_o365_json_get_string_member (group, "classId", NULL);
+}
+
+const gchar *
+e_o365_calendar_group_get_name (EO365CalendarGroup *group)
+{
+       return e_o365_json_get_string_member (group, "name", NULL);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/calendar?view=graph-rest-1.0 */
+
+const gchar *
+e_o365_calendar_get_id (EO365Calendar *calendar)
+{
+       return e_o365_json_get_string_member (calendar, "id", NULL);
+}
+
+const gchar *
+e_o365_calendar_get_change_key (EO365Calendar *calendar)
+{
+       return e_o365_json_get_string_member (calendar, "changeKey", NULL);
+}
+
+gboolean
+e_o365_calendar_get_can_edit (EO365Calendar *calendar)
+{
+       return e_o365_json_get_boolean_member (calendar, "canEdit", FALSE);
+}
+
+gboolean
+e_o365_calendar_get_can_share (EO365Calendar *calendar)
+{
+       return e_o365_json_get_boolean_member (calendar, "canShare", FALSE);
+}
+
+gboolean
+e_o365_calendar_get_can_view_private_items (EO365Calendar *calendar)
+{
+       return e_o365_json_get_boolean_member (calendar, "canViewPrivateItems", FALSE);
+}
+
+gboolean
+e_o365_calendar_get_is_removable (EO365Calendar *calendar)
+{
+       return e_o365_json_get_boolean_member (calendar, "isRemovable", FALSE);
+}
+
+gboolean
+e_o365_calendar_get_is_tallying_responses (EO365Calendar *calendar)
+{
+       return e_o365_json_get_boolean_member (calendar, "isTallyingResponses", FALSE);
+}
+
+EO365EmailAddress *
+e_o365_calendar_get_owner (EO365Calendar *calendar)
+{
+       return e_o365_json_get_object_member (calendar, "owner");
+}
+
+const gchar *
+e_o365_calendar_get_name (EO365Calendar *calendar)
+{
+       return e_o365_json_get_string_member (calendar, "name", NULL);
+}
+
+void
+e_o365_calendar_add_name (JsonBuilder *builder,
+                         const gchar *name)
+{
+       e_o365_json_add_nonempty_string_member (builder, "name", name);
+}
+
+static struct _meeting_provider_map {
+       const gchar *name;
+       EO365OnlineMeetingProviderType value;
+} meeting_provider_map[] = {
+       { "unknown",            E_O365_ONLINE_MEETING_PROVIDER_UNKNOWN },
+       { "skypeForBusiness",   E_O365_ONLINE_MEETING_PROVIDER_SKYPE_FOR_BUSINESS },
+       { "skypeForConsumer",   E_O365_ONLINE_MEETING_PROVIDER_SKYPE_FOR_CONSUMER },
+       { "teamsForBusiness",   E_O365_ONLINE_MEETING_PROVIDER_TEAMS_FOR_BUSINESS }
+};
+
+guint32 /* bit-or of EO365OnlineMeetingProviderType */
+e_o365_calendar_get_allowed_online_meeting_providers (EO365Calendar *calendar)
+{
+       guint32 providers = E_O365_ONLINE_MEETING_PROVIDER_NOT_SET;
+       JsonArray *array;
+
+       array = e_o365_json_get_array_member (calendar, "allowedOnlineMeetingProviders");
+
+       if (array) {
+               guint ii, jj, len;
+
+               providers = E_O365_ONLINE_MEETING_PROVIDER_UNKNOWN;
+
+               len = json_array_get_length (array);
+
+               for (ii = 0; ii < len; ii++) {
+                       const gchar *str = json_array_get_string_element (array, ii);
+
+                       if (!str)
+                               continue;
+
+                       for (jj = 0; jj < G_N_ELEMENTS (meeting_provider_map); jj++) {
+                               if (g_ascii_strcasecmp (str, meeting_provider_map[jj].name) == 0) {
+                                       providers |= meeting_provider_map[jj].value;
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       return providers;
+}
+
+void
+e_o365_calendar_add_allowed_online_meeting_providers (JsonBuilder *builder,
+                                                     guint providers) /* bit-or of 
EO365OnlineMeetingProviderType */
+{
+       gint ii;
+
+       if (providers == E_O365_ONLINE_MEETING_PROVIDER_NOT_SET)
+               return;
+
+       e_o365_json_begin_array_member (builder, "allowedOnlineMeetingProviders");
+
+       if (providers == E_O365_ONLINE_MEETING_PROVIDER_UNKNOWN)
+               json_builder_add_string_value (builder, "unknown");
+
+       for (ii = 0; ii < G_N_ELEMENTS (meeting_provider_map); ii++) {
+               if ((providers & meeting_provider_map[ii].value) != 0)
+                       json_builder_add_string_value (builder, meeting_provider_map[ii].name);
+       }
+
+       e_o365_json_end_array_member (builder);
+}
+
+EO365CalendarColorType
+e_o365_calendar_get_color (EO365Calendar *calendar)
+{
+       const gchar *color;
+       gint ii;
+
+       color = e_o365_json_get_string_member (calendar, "color", NULL);
+
+       if (!color)
+               return E_O365_CALENDAR_COLOR_NOT_SET;
+
+       for (ii = 0; ii < G_N_ELEMENTS (color_map); ii++) {
+               if (g_ascii_strcasecmp (color_map[ii].name, color) == 0)
+                       return color_map[ii].value;
+       }
+
+       return E_O365_CALENDAR_COLOR_UNKNOWN;
+}
+
+void
+e_o365_calendar_add_color (JsonBuilder *builder,
+                          EO365CalendarColorType color)
+{
+       const gchar *name = NULL;
+       gint ii;
+
+       for (ii = 0; ii < G_N_ELEMENTS (color_map); ii++) {
+               if (color_map[ii].value == color) {
+                       name = color_map[ii].name;
+                       break;
+               }
+       }
+
+       if (name && g_ascii_strcasecmp (name, "maxColor") != 0)
+               e_o365_json_add_string_member (builder, "color", name);
+}
+
+EO365OnlineMeetingProviderType
+e_o365_calendar_get_default_online_meeting_provider (EO365Calendar *calendar)
+{
+       const gchar *name;
+       gint ii;
+
+       name = e_o365_json_get_string_member (calendar, "defaultOnlineMeetingProvider", NULL);
+
+       if (!name)
+               return E_O365_ONLINE_MEETING_PROVIDER_NOT_SET;
+
+       for (ii = 0; ii < G_N_ELEMENTS (meeting_provider_map); ii++) {
+               if (g_ascii_strcasecmp (name, meeting_provider_map[ii].name) == 0)
+                       return meeting_provider_map[ii].value;
+       }
+
+       return E_O365_ONLINE_MEETING_PROVIDER_UNKNOWN;
+}
+
+void
+e_o365_calendar_add_default_online_meeting_provider (JsonBuilder *builder,
+                                                    EO365OnlineMeetingProviderType provider)
+{
+       const gchar *name = NULL;
+       gint ii;
+
+       for (ii = 0; ii < G_N_ELEMENTS (meeting_provider_map); ii++) {
+               if (meeting_provider_map[ii].value == provider) {
+                       name = meeting_provider_map[ii].name;
+                       break;
+               }
+       }
+
+       if (name)
+               e_o365_json_add_string_member (builder, "defaultOnlineMeetingProvider", name);
+}
diff --git a/src/Office365/common/e-o365-json-utils.h b/src/Office365/common/e-o365-json-utils.h
index 2cd13d1c..f5e51ac8 100644
--- a/src/Office365/common/e-o365-json-utils.h
+++ b/src/Office365/common/e-o365-json-utils.h
@@ -25,6 +25,8 @@ G_BEGIN_DECLS
 
 /* Just for better readability */
 #define EO365Attachment                        JsonObject
+#define EO365Calendar                  JsonObject
+#define EO365CalendarGroup             JsonObject
 #define EO365Category                  JsonObject
 #define EO365Contact                   JsonObject
 #define EO365DateTimeWithZone          JsonObject
@@ -76,6 +78,34 @@ typedef enum _EO365ItemBodyContentTypeType {
        E_O365_ITEM_BODY_CONTENT_TYPE_HTML
 } EO365ItemBodyContentTypeType;
 
+typedef enum _EO365OnlineMeetingProviderType {
+       E_O365_ONLINE_MEETING_PROVIDER_NOT_SET                  = -1,
+       E_O365_ONLINE_MEETING_PROVIDER_UNKNOWN                  = 0,
+       E_O365_ONLINE_MEETING_PROVIDER_SKYPE_FOR_BUSINESS       = 1 << 0,
+       E_O365_ONLINE_MEETING_PROVIDER_SKYPE_FOR_CONSUMER       = 1 << 1,
+       E_O365_ONLINE_MEETING_PROVIDER_TEAMS_FOR_BUSINESS       = 1 << 2
+} EO365OnlineMeetingProviderType;
+
+typedef enum _EO365CalendarColorType {
+       E_O365_CALENDAR_COLOR_NOT_SET           = -3,
+       E_O365_CALENDAR_COLOR_UNKNOWN           = -2,
+       E_O365_CALENDAR_COLOR_AUTO              = -1,
+       E_O365_CALENDAR_COLOR_LIGHT_BLUE        = 0,
+       E_O365_CALENDAR_COLOR_LIGHT_GREEN       = 1,
+       E_O365_CALENDAR_COLOR_LIGHT_ORANGE      = 2,
+       E_O365_CALENDAR_COLOR_LIGHT_GRAY        = 3,
+       E_O365_CALENDAR_COLOR_LIGHT_YELLOW      = 4,
+       E_O365_CALENDAR_COLOR_LIGHT_TEAL        = 5,
+       E_O365_CALENDAR_COLOR_LIGHT_PINK        = 6,
+       E_O365_CALENDAR_COLOR_LIGHT_BROWN       = 7,
+       E_O365_CALENDAR_COLOR_LIGHT_RED         = 8,
+       E_O365_CALENDAR_COLOR_MAX_COLOR         = 9
+} EO365CalendarColorType;
+
+const gchar *  e_o365_calendar_color_to_rgb            (EO365CalendarColorType color);
+EO365CalendarColorType
+               e_o365_rgb_to_calendar_color            (const gchar *rgb);
+
 JsonArray *    e_o365_json_get_array_member            (JsonObject *object,
                                                         const gchar *member_name);
 void           e_o365_json_begin_array_member          (JsonBuilder *builder,
@@ -466,6 +496,41 @@ const gchar *      e_o365_contact_get_yomi_surname         (EO365Contact *contact);
 void           e_o365_contact_add_yomi_surname         (JsonBuilder *builder,
                                                         const gchar *value);
 
+const gchar *  e_o365_calendar_group_get_id            (EO365CalendarGroup *group);
+const gchar *  e_o365_calendar_group_get_change_key    (EO365CalendarGroup *group);
+const gchar *  e_o365_calendar_group_get_class_id      (EO365CalendarGroup *group);
+const gchar *  e_o365_calendar_group_get_name          (EO365CalendarGroup *group);
+
+const gchar *  e_o365_calendar_get_id                  (EO365Calendar *calendar);
+const gchar *  e_o365_calendar_get_change_key          (EO365Calendar *calendar);
+gboolean       e_o365_calendar_get_can_edit            (EO365Calendar *calendar);
+gboolean       e_o365_calendar_get_can_share           (EO365Calendar *calendar);
+gboolean       e_o365_calendar_get_can_view_private_items
+                                                       (EO365Calendar *calendar);
+gboolean       e_o365_calendar_get_is_removable        (EO365Calendar *calendar);
+gboolean       e_o365_calendar_get_is_tallying_responses
+                                                       (EO365Calendar *calendar);
+EO365EmailAddress *
+               e_o365_calendar_get_owner               (EO365Calendar *calendar);
+const gchar *  e_o365_calendar_get_name                (EO365Calendar *calendar);
+void           e_o365_calendar_add_name                (JsonBuilder *builder,
+                                                        const gchar *name);
+guint32                e_o365_calendar_get_allowed_online_meeting_providers /* bit-or of 
EO365OnlineMeetingProviderType */
+                                                       (EO365Calendar *calendar);
+void           e_o365_calendar_add_allowed_online_meeting_providers
+                                                       (JsonBuilder *builder,
+                                                        guint providers); /* bit-or of 
EO365OnlineMeetingProviderType */
+EO365CalendarColorType
+               e_o365_calendar_get_color               (EO365Calendar *calendar);
+void           e_o365_calendar_add_color               (JsonBuilder *builder,
+                                                        EO365CalendarColorType color);
+EO365OnlineMeetingProviderType
+               e_o365_calendar_get_default_online_meeting_provider
+                                                       (EO365Calendar *calendar);
+void           e_o365_calendar_add_default_online_meeting_provider
+                                                       (JsonBuilder *builder,
+                                                        EO365OnlineMeetingProviderType provider);
+
 G_END_DECLS
 
 #endif /* E_O365_JSON_UTILS_H */
diff --git a/src/Office365/common/e-source-o365-folder.c b/src/Office365/common/e-source-o365-folder.c
index 8d7ca61e..d8321f5a 100644
--- a/src/Office365/common/e-source-o365-folder.c
+++ b/src/Office365/common/e-source-o365-folder.c
@@ -21,13 +21,15 @@
 
 struct _ESourceO365FolderPrivate {
        gchar *id;
+       gchar *group_id;
        gboolean is_default;
 };
 
 enum {
        PROP_0,
        PROP_ID,
-       PROP_IS_DEFAULT
+       PROP_IS_DEFAULT,
+       PROP_GROUP_ID
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (ESourceO365Folder, e_source_o365_folder, E_TYPE_SOURCE_EXTENSION)
@@ -50,6 +52,12 @@ source_o365_folder_set_property (GObject *object,
                                E_SOURCE_O365_FOLDER (object),
                                g_value_get_string (value));
                        return;
+
+               case PROP_GROUP_ID:
+                       e_source_o365_folder_set_group_id (
+                               E_SOURCE_O365_FOLDER (object),
+                               g_value_get_string (value));
+                       return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -75,6 +83,13 @@ source_o365_folder_get_property (GObject *object,
                                e_source_o365_folder_dup_id (
                                E_SOURCE_O365_FOLDER (object)));
                        return;
+
+               case PROP_GROUP_ID:
+                       g_value_take_string (
+                               value,
+                               e_source_o365_folder_dup_group_id (
+                               E_SOURCE_O365_FOLDER (object)));
+                       return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -86,6 +101,7 @@ source_o365_folder_finalize (GObject *object)
        ESourceO365Folder *o365_folder = E_SOURCE_O365_FOLDER (object);
 
        g_free (o365_folder->priv->id);
+       g_free (o365_folder->priv->group_id);
 
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (e_source_o365_folder_parent_class)->finalize (object);
@@ -130,6 +146,19 @@ e_source_o365_folder_class_init (ESourceO365FolderClass *class)
                        G_PARAM_CONSTRUCT |
                        G_PARAM_STATIC_STRINGS |
                        E_SOURCE_PARAM_SETTING));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_GROUP_ID,
+               g_param_spec_string (
+                       "group-id",
+                       "Group ID",
+                       "Optional group ID, into which the folder ID belongs",
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT |
+                       G_PARAM_STATIC_STRINGS |
+                       E_SOURCE_PARAM_SETTING));
 }
 
 static void
@@ -220,3 +249,50 @@ e_source_o365_folder_set_is_default (ESourceO365Folder *extension,
 
        g_object_notify (G_OBJECT (extension), "is-default");
 }
+
+const gchar *
+e_source_o365_folder_get_group_id (ESourceO365Folder *extension)
+{
+       g_return_val_if_fail (E_IS_SOURCE_O365_FOLDER (extension), NULL);
+
+       return extension->priv->group_id;
+}
+
+gchar *
+e_source_o365_folder_dup_group_id (ESourceO365Folder *extension)
+{
+       const gchar *protected;
+       gchar *duplicate;
+
+       g_return_val_if_fail (E_IS_SOURCE_O365_FOLDER (extension), NULL);
+
+       e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
+
+       protected = e_source_o365_folder_get_group_id (extension);
+       duplicate = g_strdup (protected);
+
+       e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+
+       return duplicate;
+}
+
+void
+e_source_o365_folder_set_group_id (ESourceO365Folder *extension,
+                                  const gchar *group_id)
+{
+       g_return_if_fail (E_IS_SOURCE_O365_FOLDER (extension));
+
+       e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
+
+       if (g_strcmp0 (extension->priv->group_id, group_id) == 0) {
+               e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+               return;
+       }
+
+       g_free (extension->priv->group_id);
+       extension->priv->group_id = g_strdup (group_id);
+
+       e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+
+       g_object_notify (G_OBJECT (extension), "group-id");
+}
diff --git a/src/Office365/common/e-source-o365-folder.h b/src/Office365/common/e-source-o365-folder.h
index 4b1ddc63..2a7ca84a 100644
--- a/src/Office365/common/e-source-o365-folder.h
+++ b/src/Office365/common/e-source-o365-folder.h
@@ -68,6 +68,13 @@ gboolean     e_source_o365_folder_get_is_default
 void           e_source_o365_folder_set_is_default
                                                (ESourceO365Folder *extension,
                                                 gboolean value);
+const gchar *  e_source_o365_folder_get_group_id
+                                               (ESourceO365Folder *extension);
+gchar *                e_source_o365_folder_dup_group_id
+                                               (ESourceO365Folder *extension);
+void           e_source_o365_folder_set_group_id
+                                               (ESourceO365Folder *extension,
+                                                const gchar *group_id);
 
 G_END_DECLS
 
diff --git a/src/Office365/registry/e-o365-backend.c b/src/Office365/registry/e-o365-backend.c
index 20f85b68..a2dc10fb 100644
--- a/src/Office365/registry/e-o365-backend.c
+++ b/src/Office365/registry/e-o365-backend.c
@@ -110,6 +110,7 @@ static void
 o365_backend_update_resource (EO365Backend *o365_backend,
                              const gchar *extension_name,
                              const gchar *id,
+                             const gchar *group_id,
                              const gchar *display_name,
                              gboolean is_default,
                              const gchar *calendar_color)
@@ -171,6 +172,7 @@ o365_backend_update_resource (EO365Backend *o365_backend,
 
                        extension = e_source_get_extension (source, E_SOURCE_EXTENSION_O365_FOLDER);
                        e_source_o365_folder_set_id (extension, id);
+                       e_source_o365_folder_set_group_id (extension, group_id);
                        e_source_o365_folder_set_is_default (extension, is_default);
 
                        server = e_collection_backend_ref_server (E_COLLECTION_BACKEND (o365_backend));
@@ -222,34 +224,69 @@ o365_backend_remove_resource (EO365Backend *o365_backend,
        g_clear_object (&existing_source);
 }
 
-static void
-o365_backend_forget_folders (EO365Backend *o365_backend,
-                            const gchar *extension_name)
+static GHashTable * /* gchar *uid ~> NULL */
+o365_backend_get_known_folder_ids (EO365Backend *o365_backend,
+                                  const gchar *extension_name,
+                                  gboolean with_the_default)
 {
+       GHashTable *ids;
        GHashTableIter iter;
-       GSList *ids = NULL, *link;
        gpointer value;
 
+       ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
        LOCK (o365_backend);
 
        g_hash_table_iter_init (&iter, o365_backend->priv->folder_sources);
+
        while (g_hash_table_iter_next (&iter, NULL, &value)) {
                ESource *source = value;
 
-               if (source && e_source_has_extension (source, extension_name))
-                       ids = g_slist_prepend (ids, e_source_o365_folder_dup_id (e_source_get_extension 
(source, E_SOURCE_EXTENSION_O365_FOLDER)));
+               if (source && e_source_has_extension (source, extension_name)) {
+                       ESourceO365Folder *o365_folder;
+
+                       o365_folder = e_source_get_extension (source, E_SOURCE_EXTENSION_O365_FOLDER);
+
+                       if (with_the_default || !e_source_o365_folder_get_is_default (o365_folder))
+                               g_hash_table_insert (ids, e_source_o365_folder_dup_id (o365_folder), NULL);
+               }
        }
 
        UNLOCK (o365_backend);
 
-       for (link = ids; link; link = g_slist_next (link)) {
-               const gchar *id = link->data;
+       return ids;
+}
+
+static void
+o365_backend_forget_folders_hash (EO365Backend *o365_backend,
+                                 const gchar *extension_name,
+                                 GHashTable *ids) /* gchar *id ~> NULL */
+{
+       GHashTableIter iter;
+       gpointer key;
+
+       g_hash_table_iter_init (&iter, ids);
+
+       while (g_hash_table_iter_next (&iter, &key, NULL)) {
+               const gchar *id = key;
 
                if (id)
                        o365_backend_remove_resource (o365_backend, extension_name, id);
        }
+}
+
+static void
+o365_backend_forget_folders (EO365Backend *o365_backend,
+                            const gchar *extension_name,
+                            gboolean with_the_default)
+{
+       GHashTable *ids;
 
-       g_slist_free_full (ids, g_free);
+       ids = o365_backend_get_known_folder_ids (o365_backend, extension_name, with_the_default);
+
+       o365_backend_forget_folders_hash (o365_backend, extension_name, ids);
+
+       g_hash_table_destroy (ids);
 }
 
 static gboolean
@@ -275,7 +312,7 @@ o365_backend_got_contact_folders_delta_cb (EO365Connection *cnc,
                        o365_backend_remove_resource (o365_backend, E_SOURCE_EXTENSION_ADDRESS_BOOK, id);
                } else {
                        o365_backend_update_resource (o365_backend, E_SOURCE_EXTENSION_ADDRESS_BOOK,
-                             id, e_o365_folder_get_display_name (object),
+                             id, NULL, e_o365_folder_get_display_name (object),
                              FALSE, NULL);
                }
        }
@@ -284,25 +321,19 @@ o365_backend_got_contact_folders_delta_cb (EO365Connection *cnc,
 }
 
 static void
-o365_backend_sync_folders_thread (GTask *task,
-                                 gpointer source_object,
-                                 gpointer task_data,
-                                 GCancellable *cancellable)
+o365_backend_sync_contact_folders_sync (EO365Backend *o365_backend,
+                                       EO365Connection *cnc,
+                                       GCancellable *cancellable)
 {
-       EO365Backend *o365_backend = source_object;
-       EO365Connection *cnc = task_data;
-       ESourceO365Deltas *o365_deltas;
        EO365Folder *user_contacts = NULL;
+       ESourceO365Deltas *o365_deltas;
        gchar *old_delta_link, *new_delta_link;
        gboolean success;
        GError *error = NULL;
 
-       g_return_if_fail (E_IS_O365_BACKEND (o365_backend));
-       g_return_if_fail (E_IS_O365_CONNECTION (cnc));
-
        o365_deltas = e_source_get_extension (e_backend_get_source (E_BACKEND (o365_backend)), 
E_SOURCE_EXTENSION_O365_DELTAS);
 
-       if (e_o365_connection_get_contacts_folder_sync (cnc, NULL, &user_contacts, cancellable, &error)) {
+       if (e_o365_connection_get_contacts_folder_sync (cnc, NULL, NULL, NULL, &user_contacts, cancellable, 
&error)) {
                const gchar *id, *display_name;
 
                id = e_o365_folder_get_id (user_contacts);
@@ -313,7 +344,7 @@ o365_backend_sync_folders_thread (GTask *task,
 
                o365_backend_update_resource (o365_backend,
                        E_SOURCE_EXTENSION_ADDRESS_BOOK,
-                       id, display_name, TRUE, NULL);
+                       id, NULL, display_name, TRUE, NULL);
 
                json_object_unref (user_contacts);
        } else if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND) ||
@@ -333,7 +364,7 @@ o365_backend_sync_folders_thread (GTask *task,
                g_clear_pointer (&old_delta_link, g_free);
                g_clear_error (&error);
 
-               o365_backend_forget_folders (o365_backend, E_SOURCE_EXTENSION_ADDRESS_BOOK);
+               o365_backend_forget_folders (o365_backend, E_SOURCE_EXTENSION_ADDRESS_BOOK, FALSE);
 
                success = e_o365_connection_get_folders_delta_sync (cnc, NULL, E_O365_FOLDER_KIND_CONTACTS, 
NULL, NULL, 0,
                        o365_backend_got_contact_folders_delta_cb, o365_backend, &new_delta_link, 
cancellable, &error);
@@ -347,6 +378,88 @@ o365_backend_sync_folders_thread (GTask *task,
        g_clear_error (&error);
 }
 
+static void
+o365_backend_sync_calendar_folders_sync (EO365Backend *o365_backend,
+                                        EO365Connection *cnc,
+                                        GCancellable *cancellable)
+{
+       const gchar *extension_name = E_SOURCE_EXTENSION_CALENDAR;
+       GHashTable *known_ids; /* gchar *id ~> NULL */
+       gboolean success = FALSE;
+       GSList *groups = NULL, *link;
+       GError *error = NULL;
+
+       known_ids = o365_backend_get_known_folder_ids (o365_backend, extension_name, FALSE);
+
+       if (e_o365_connection_list_calendar_groups_sync (cnc, NULL, &groups, cancellable, &error) && groups) {
+               success = TRUE;
+
+               for (link = groups; link && success; link = g_slist_next (link)) {
+                       EO365CalendarGroup *group = link->data;
+                       GSList *calendars = NULL;
+
+                       if (!group)
+                               continue;
+
+                       if (e_o365_connection_list_calendars_sync (cnc, NULL, e_o365_calendar_group_get_id 
(group), NULL, &calendars, cancellable, &error)) {
+                               GSList *clink;
+
+                               for (clink = calendars; clink; clink = g_slist_next (clink)) {
+                                       EO365Calendar *calendar = clink->data;
+
+                                       if (!calendar || !e_o365_calendar_get_id (calendar))
+                                               continue;
+
+                                       o365_backend_update_resource (o365_backend, extension_name,
+                                               e_o365_calendar_get_id (calendar),
+                                               e_o365_calendar_group_get_id (group),
+                                               e_o365_calendar_get_name (calendar),
+                                               FALSE,
+                                               e_o365_calendar_color_to_rgb (e_o365_calendar_get_color 
(calendar)));
+
+                                       g_hash_table_remove (known_ids, e_o365_calendar_get_id (calendar));
+                               }
+                       } else {
+                               success = FALSE;
+                       }
+               }
+
+               g_slist_free_full (groups, (GDestroyNotify) json_object_unref);
+       }
+
+       if (success)
+               o365_backend_forget_folders_hash (o365_backend, extension_name, known_ids);
+
+       g_hash_table_destroy (known_ids);
+       g_clear_error (&error);
+}
+
+static void
+o365_backend_sync_folders_thread (GTask *task,
+                                 gpointer source_object,
+                                 gpointer task_data,
+                                 GCancellable *cancellable)
+{
+       EO365Backend *o365_backend = source_object;
+       EO365Connection *cnc = task_data;
+       ESourceCollection *collection_extension = NULL;
+       ESource *source;
+
+       g_return_if_fail (E_IS_O365_BACKEND (o365_backend));
+       g_return_if_fail (E_IS_O365_CONNECTION (cnc));
+
+       source = e_backend_get_source (E_BACKEND (o365_backend));
+       collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION);
+
+       if (e_source_collection_get_contacts_enabled (collection_extension)) {
+               o365_backend_sync_contact_folders_sync (o365_backend, cnc, cancellable);
+       }
+
+       if (e_source_collection_get_calendar_enabled (collection_extension)) {
+               o365_backend_sync_calendar_folders_sync (o365_backend, cnc, cancellable);
+       }
+}
+
 static void
 o365_backend_sync_folders (EO365Backend *o365_backend,
                           EO365Connection *cnc,
@@ -663,7 +776,7 @@ o365_backend_authenticate_sync (EBackend *backend,
 
        cnc = e_o365_connection_new (e_backend_get_source (backend), o365_settings);
 
-       result = e_o365_connection_authenticate_sync (cnc, NULL, E_O365_FOLDER_KIND_UNKNOWN, NULL, 
out_certificate_pem, out_certificate_errors, cancellable, error);
+       result = e_o365_connection_authenticate_sync (cnc, NULL, E_O365_FOLDER_KIND_UNKNOWN, NULL, NULL, 
out_certificate_pem, out_certificate_errors, cancellable, error);
 
        if (result == E_SOURCE_AUTHENTICATION_ACCEPTED) {
                e_collection_backend_authenticate_children (E_COLLECTION_BACKEND (backend), credentials);


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