[evolution-ews/wip/mcrha/office365] Add user contacts (address book) read (get_changes())



commit a7972900914148c67c01667261fa65f6a0fa36e7
Author: Milan Crha <mcrha redhat com>
Date:   Mon Jul 13 19:02:22 2020 +0200

    Add user contacts (address book) read (get_changes())

 src/Office365/addressbook/e-book-backend-o365.c | 1390 ++++++++++++++++++++++-
 src/Office365/camel/camel-o365-folder.c         |    6 +-
 src/Office365/camel/camel-o365-store.c          |    6 +-
 src/Office365/camel/camel-o365-transport.c      |    2 +-
 src/Office365/common/e-o365-connection.c        |  246 +++-
 src/Office365/common/e-o365-connection.h        |   27 +-
 src/Office365/common/e-o365-json-utils.c        |  678 ++++++++++-
 src/Office365/common/e-o365-json-utils.h        |  184 ++-
 src/Office365/registry/e-o365-backend.c         |    6 +-
 9 files changed, 2427 insertions(+), 118 deletions(-)
---
diff --git a/src/Office365/addressbook/e-book-backend-o365.c b/src/Office365/addressbook/e-book-backend-o365.c
index d60003ba..bddd8d6a 100644
--- a/src/Office365/addressbook/e-book-backend-o365.c
+++ b/src/Office365/addressbook/e-book-backend-o365.c
@@ -17,6 +17,9 @@
 
 #include "evolution-ews-config.h"
 
+#include <string.h>
+#include <time.h>
+
 #include <glib.h>
 #include <glib/gi18n-lib.h>
 
@@ -27,6 +30,8 @@
 #include <libedata-book/libedata-book.h>
 
 #include "common/camel-o365-settings.h"
+#include "common/e-o365-connection.h"
+#include "common/e-source-o365-folder.h"
 
 #include "e-book-backend-o365.h"
 
@@ -39,20 +44,1159 @@
 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
 #endif
 
-#define d(x)
-
 #define EC_ERROR_EX(_code,_msg) e_client_error_create (_code, _msg)
 #define EBC_ERROR_EX(_code,_msg) e_book_client_error_create (_code, _msg)
 
 #define EBB_O365_DATA_VERSION 1
 #define EBB_O365_DATA_VERSION_KEY "o365-data-version"
 
+#define LOCK(_bb) g_rec_mutex_lock (&_bb->priv->property_lock)
+#define UNLOCK(_bb) g_rec_mutex_unlock (&_bb->priv->property_lock)
+
 struct _EBookBackendO365Private {
        GRecMutex property_lock;
+       EO365Connection *cnc;
+       gchar *folder_id;
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (EBookBackendO365, e_book_backend_o365, E_TYPE_BOOK_META_BACKEND)
 
+static void
+ebb_o365_contact_get_string_attribute (EO365Contact *o365_contact,
+                                      EContact *inout_contact,
+                                      EContactField field_id,
+                                      const gchar * (*o365_get_func) (EO365Contact *contact))
+{
+       e_contact_set (inout_contact, field_id, o365_get_func (o365_contact));
+}
+
+static void
+ebb_o365_contact_add_string_attribute (EContact *new_contact,
+                                      EContact *old_contact,
+                                      EContactField field_id,
+                                      JsonBuilder *builder,
+                                      void (* o365_add_func) (JsonBuilder *builder,
+                                                              const gchar *value))
+{
+       const gchar *new_value, *old_value;
+
+       g_return_if_fail (o365_add_func != NULL);
+
+       new_value = e_contact_get_const (new_contact, field_id);
+       old_value = old_contact ? e_contact_get_const (old_contact, field_id) : NULL;
+
+       if (g_strcmp0 (new_value, old_value) != 0)
+               o365_add_func (builder, new_value);
+}
+
+static gboolean
+ebb_o365_contact_get_rev (EBookBackendO365 *bbo365,
+                         EO365Contact *o365_contact,
+                         EContact *inout_contact,
+                         EContactField field_id,
+                         EO365Connection *cnc,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       gchar time_string[100] = { 0 };
+       struct tm stm;
+       time_t value;
+
+       value = e_o365_contact_get_last_modified_date_time (o365_contact);
+
+       if (value <= (time_t) 0)
+               value = time (NULL);
+
+       gmtime_r (&value, &stm);
+       strftime (time_string, 100, "%Y-%m-%dT%H:%M:%SZ", &stm);
+
+       e_contact_set (inout_contact, field_id, time_string);
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_get_birthday (EBookBackendO365 *bbo365,
+                              EO365Contact *o365_contact,
+                              EContact *inout_contact,
+                              EContactField field_id,
+                              EO365Connection *cnc,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       time_t value;
+
+       value = e_o365_contact_get_birthday (o365_contact);
+
+       if (value > (time_t) 0) {
+               EContactDate dt;
+               struct tm stm;
+
+               gmtime_r (&value, &stm);
+
+               dt.year = stm.tm_year + 1900;
+               dt.month = stm.tm_mon + 1;
+               dt.day = stm.tm_mday;
+
+               e_contact_set (inout_contact, field_id, &dt);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_add_birthday (EBookBackendO365 *bbo365,
+                              EContact *new_contact,
+                              EContact *old_contact,
+                              EContactField field_id,
+                              JsonBuilder *builder,
+                              EO365Connection *cnc,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       EContactDate *old_dt = NULL;
+       EContactDate *new_dt = NULL;
+
+       new_dt = e_contact_get (new_contact, field_id);
+       old_dt = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+       if (!e_contact_date_equal (new_dt, old_dt)) {
+               if (new_dt) {
+                       GDateTime *gdt;
+
+                       gdt = g_date_time_new_local (new_dt->year, new_dt->month, new_dt->day, 0, 0, 0.0);
+
+                       if (gdt) {
+                               e_o365_contact_add_birthday (builder, g_date_time_to_unix (gdt));
+
+                               g_date_time_unref (gdt);
+                       } else {
+                               e_o365_contact_add_birthday (builder, (time_t) 0);
+                       }
+               } else {
+                       e_o365_contact_add_birthday (builder, (time_t) 0);
+               }
+       }
+
+       e_contact_date_free (new_dt);
+       e_contact_date_free (old_dt);
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_get_address (EBookBackendO365 *bbo365,
+                             EO365Contact *o365_contact,
+                             EContact *inout_contact,
+                             EContactField field_id,
+                             EO365Connection *cnc,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       EO365PhysicalAddress *phys_address = NULL;
+
+       if (field_id == E_CONTACT_ADDRESS_WORK)
+               phys_address = e_o365_contact_get_business_address (o365_contact);
+       else if (field_id == E_CONTACT_ADDRESS_HOME)
+               phys_address = e_o365_contact_get_home_address (o365_contact);
+       else if (field_id == E_CONTACT_ADDRESS_OTHER)
+               phys_address = e_o365_contact_get_other_address (o365_contact);
+       else
+               g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_field_name (field_id));
+
+       if (phys_address) {
+               EContactAddress addr;
+
+               memset (&addr, 0, sizeof (EContactAddress));
+
+               addr.locality = (gchar *) e_o365_physical_address_get_city (phys_address);
+               addr.country = (gchar *) e_o365_physical_address_get_country_or_region (phys_address);
+               addr.code = (gchar *) e_o365_physical_address_get_postal_code (phys_address);
+               addr.region = (gchar *) e_o365_physical_address_get_state (phys_address);
+               addr.street = (gchar *) e_o365_physical_address_get_street (phys_address);
+
+               if (addr.locality || addr.country || addr.code || addr.region || addr.street)
+                       e_contact_set (inout_contact, field_id, &addr);
+               else
+                       e_contact_set (inout_contact, field_id, NULL);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_address_equal (const EContactAddress *addr1,
+                               const EContactAddress *addr2)
+{
+       if (!addr1 && !addr2)
+               return TRUE;
+
+       if ((addr1 && !addr2) || (!addr1 && addr2))
+               return FALSE;
+
+       return /* g_strcmp0 (addr1->address_format, addr2->address_format) == 0 && */
+               g_strcmp0 (addr1->po, addr2->po) == 0 &&
+               g_strcmp0 (addr1->ext, addr2->ext) == 0 &&
+               g_strcmp0 (addr1->street, addr2->street) == 0 &&
+               g_strcmp0 (addr1->locality, addr2->locality) == 0 &&
+               g_strcmp0 (addr1->region, addr2->region) == 0 &&
+               g_strcmp0 (addr1->code, addr2->code) == 0 &&
+               g_strcmp0 (addr1->country, addr2->country) == 0;
+}
+
+static gboolean
+ebb_o365_contact_add_address (EBookBackendO365 *bbo365,
+                             EContact *new_contact,
+                             EContact *old_contact,
+                             EContactField field_id,
+                             JsonBuilder *builder,
+                             EO365Connection *cnc,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       EContactAddress *new_addr, *old_addr;
+
+       new_addr = e_contact_get (new_contact, field_id);
+       old_addr = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+       if (!ebb_o365_contact_address_equal (new_addr, old_addr)) {
+               void (* add_func) (JsonBuilder *builder,
+                                  const gchar *city,
+                                  const gchar *country_or_region,
+                                  const gchar *postal_code,
+                                  const gchar *state,
+                                  const gchar *street) = NULL;
+
+               if (field_id == E_CONTACT_ADDRESS_WORK)
+                       add_func = e_o365_contact_add_business_address;
+               else if (field_id == E_CONTACT_ADDRESS_HOME)
+                       add_func = e_o365_contact_add_home_address;
+               else if (field_id == E_CONTACT_ADDRESS_OTHER)
+                       add_func = e_o365_contact_add_other_address;
+               else
+                       g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_field_name (field_id));
+
+               if (add_func) {
+                       if (new_addr) {
+                               add_func (builder, new_addr->locality, new_addr->country, new_addr->code, 
new_addr->region, new_addr->street);
+                       } else {
+                               add_func (builder, NULL, NULL, NULL, NULL, NULL);
+                       }
+               }
+       }
+
+       e_contact_address_free (new_addr);
+       e_contact_address_free (old_addr);
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_string_values_equal (GSList *new_values, /* const gchar * */
+                             GSList *old_values) /* const gchar * */
+{
+       GHashTable *values;
+       GSList *link;
+       gboolean equal = TRUE;
+
+       if (g_slist_length (new_values) != g_slist_length (old_values))
+               return FALSE;
+
+       values = g_hash_table_new (g_str_hash, g_str_equal);
+
+       for (link = new_values; link; link = g_slist_next (link)) {
+               gchar *value = link->data;
+
+               if (value)
+                       g_hash_table_add (values, value);
+       }
+
+       for (link = old_values; link && equal; link = g_slist_next (link)) {
+               const gchar *value = link->data;
+
+               if (value)
+                       equal = g_hash_table_remove (values, value);
+       }
+
+       equal = equal && !g_hash_table_size (values);
+
+       g_hash_table_destroy (values);
+
+       return equal;
+}
+
+static gboolean
+ebb_o365_string_list_values_equal (GList *new_values, /* const gchar * */
+                                  GList *old_values) /* const gchar * */
+{
+       GHashTable *values;
+       GList *link;
+       gboolean equal = TRUE;
+
+       if (g_list_length (new_values) != g_list_length (old_values))
+               return FALSE;
+
+       values = g_hash_table_new (g_str_hash, g_str_equal);
+
+       for (link = new_values; link; link = g_list_next (link)) {
+               gchar *value = link->data;
+
+               if (value)
+                       g_hash_table_add (values, value);
+       }
+
+       for (link = old_values; link && equal; link = g_list_next (link)) {
+               const gchar *value = link->data;
+
+               if (value)
+                       equal = g_hash_table_remove (values, value);
+       }
+
+       equal = equal && !g_hash_table_size (values);
+
+       g_hash_table_destroy (values);
+
+       return equal;
+}
+
+static gboolean
+ebb_o365_contact_get_phone (EBookBackendO365 *bbo365,
+                           EO365Contact *o365_contact,
+                           EContact *inout_contact,
+                           EContactField field_id,
+                           EO365Connection *cnc,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       JsonArray *values = NULL;
+       const gchar *type_val = NULL;
+
+       if (field_id == E_CONTACT_PHONE_BUSINESS) {
+               values = e_o365_contact_get_business_phones (o365_contact);
+               type_val = "WORK";
+       } else if (field_id == E_CONTACT_PHONE_HOME) {
+               values = e_o365_contact_get_home_phones (o365_contact);
+               type_val = "HOME";
+       } else {
+               g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_field_name (field_id));
+       }
+
+       if (values) {
+               EVCard *vcard = E_VCARD (inout_contact);
+               guint ii, len;
+
+               len = json_array_get_length (values);
+
+               for (ii = 0; ii < len; ii++) {
+                       const gchar *str = json_array_get_string_element (values, ii);
+
+                       if (str && *str) {
+                               EVCardAttributeParam *param;
+                               EVCardAttribute *attr;
+
+                               attr = e_vcard_attribute_new (NULL, EVC_TEL);
+                               param = e_vcard_attribute_param_new (EVC_TYPE);
+
+                               e_vcard_attribute_add_param_with_value (attr, param, type_val);
+                               e_vcard_add_attribute_with_value (vcard, attr, str);
+                       }
+               }
+       }
+
+       return TRUE;
+}
+
+static GSList * /* gchar * */
+ebb_o365_extract_phones (EContact *contact,
+                        const gchar *only_type) /* NULL for anything but known types */
+{
+       GSList *phones = NULL;
+       GList *attrs, *link;
+
+       if (!contact)
+               return NULL;
+
+       attrs = e_vcard_get_attributes (E_VCARD (contact));
+
+       for (link = attrs; link; link = g_list_next (link)) {
+               EVCardAttribute *attr = link->data;
+               gboolean use_it = FALSE;
+
+               if (!attr || !e_vcard_attribute_get_name (attr) ||
+                   g_ascii_strcasecmp (e_vcard_attribute_get_name (attr), EVC_TEL) != 0)
+                       continue;
+
+               if (only_type) {
+                       use_it = e_vcard_attribute_has_type (attr, only_type);
+               } else {
+                       use_it = !e_vcard_attribute_has_type (attr, "WORK");
+               }
+
+               if (use_it)
+                       phones = g_slist_prepend (phones, e_vcard_attribute_get_value (attr));
+       }
+
+       return g_slist_reverse (phones);
+}
+
+static gboolean
+ebb_o365_contact_add_phone (EBookBackendO365 *bbo365,
+                           EContact *new_contact,
+                           EContact *old_contact,
+                           EContactField field_id,
+                           JsonBuilder *builder,
+                           EO365Connection *cnc,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       void (* begin_func) (JsonBuilder *builder) = NULL;
+       void (* end_func) (JsonBuilder *builder) = NULL;
+       void (* add_func) (JsonBuilder *builder, const gchar *value) = NULL;
+       const gchar *type_val = NULL;
+       GSList *new_values, *old_values;
+
+       if (field_id == E_CONTACT_PHONE_BUSINESS) {
+               begin_func = e_o365_contact_begin_business_phones;
+               end_func = e_o365_contact_end_business_phones;
+               add_func = e_o365_contact_add_business_phone;
+               type_val = "WORK";
+       } else if (field_id == E_CONTACT_PHONE_HOME) {
+               begin_func = e_o365_contact_begin_home_phones;
+               end_func = e_o365_contact_end_home_phones;
+               add_func = e_o365_contact_add_home_phone;
+               type_val = NULL; /* everythign else is treated as "HOME" phone */
+       } else {
+               g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_field_name (field_id));
+       }
+
+       new_values = ebb_o365_extract_phones (new_contact, type_val);
+       old_values = ebb_o365_extract_phones (old_contact, type_val);
+
+       if (!ebb_o365_string_values_equal (new_values, old_values)) {
+               GSList *link;
+
+               begin_func (builder);
+
+               for (link = new_values; link; link = g_slist_next (link)) {
+                       const gchar *value = link->data;
+
+                       add_func (builder, value);
+               }
+
+               end_func (builder);
+       }
+
+       g_slist_free_full (new_values, g_free);
+       g_slist_free_full (old_values, g_free);
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_get_categories (EBookBackendO365 *bbo365,
+                                EO365Contact *o365_contact,
+                                EContact *inout_contact,
+                                EContactField field_id,
+                                EO365Connection *cnc,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       JsonArray *values;
+
+       values = e_o365_contact_get_categories (o365_contact);
+
+       if (values) {
+               GString *categories_str = NULL;
+               guint ii, len;
+
+               len = json_array_get_length (values);
+
+               for (ii = 0; ii < len; ii++) {
+                       const gchar *str = json_array_get_string_element (values, ii);
+
+                       if (str && *str) {
+                               if (!categories_str) {
+                                       categories_str = g_string_new (str);
+                               } else {
+                                       g_string_append_c (categories_str,  ',');
+                                       g_string_append (categories_str, str);
+                               }
+                       }
+               }
+
+               if (categories_str) {
+                       e_contact_set (inout_contact, field_id, categories_str->str);
+                       g_string_free (categories_str, TRUE);
+               }
+       }
+
+       return TRUE;
+}
+
+static GSList *
+ebb_o365_extract_categories (EContact *contact,
+                            EContactField field_id)
+{
+       GSList *categories = NULL;
+       const gchar *str;
+
+       if (!contact)
+               return NULL;
+
+       str = e_contact_get_const (contact, field_id);
+
+       if (str && *str) {
+               gchar **split_str;
+               gint ii;
+
+               split_str = g_strsplit (str, ",", -1);
+
+               for (ii = 0; split_str && split_str[ii]; ii++) {
+                       gchar *item = split_str[ii];
+
+                       if (item && *item)
+                               categories = g_slist_prepend (categories, item);
+                       else
+                               g_free (item);
+
+                       split_str[ii] = NULL;
+               }
+
+               g_free (split_str);
+       }
+
+       return g_slist_reverse (categories);
+}
+
+static gboolean
+ebb_o365_contact_add_categories (EBookBackendO365 *bbo365,
+                                EContact *new_contact,
+                                EContact *old_contact,
+                                EContactField field_id,
+                                JsonBuilder *builder,
+                                EO365Connection *cnc,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       GSList *new_values, *old_values;
+
+       new_values = ebb_o365_extract_categories (new_contact, field_id);
+       old_values = ebb_o365_extract_categories (old_contact, field_id);
+
+       if (!ebb_o365_string_values_equal (new_values, old_values)) {
+               GSList *link;
+
+               e_o365_contact_begin_categories (builder);
+
+               for (link = new_values; link; link = g_slist_next (link)) {
+                       const gchar *value = link->data;
+
+                       e_o365_contact_add_category (builder, value);
+               }
+
+               e_o365_contact_end_categories (builder);
+       }
+
+       g_slist_free_full (new_values, g_free);
+       g_slist_free_full (old_values, g_free);
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_get_emails (EBookBackendO365 *bbo365,
+                            EO365Contact *o365_contact,
+                            EContact *inout_contact,
+                            EContactField field_id,
+                            EO365Connection *cnc,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       JsonArray *values;
+
+       values = e_o365_contact_get_categories (o365_contact);
+
+       if (values) {
+               EVCard *vcard = E_VCARD (inout_contact);
+               guint ii, len;
+
+               len = json_array_get_length (values);
+
+               for (ii = 0; ii < len; ii++) {
+                       const gchar *str = json_array_get_string_element (values, ii);
+
+                       if (str && *str) {
+                               EVCardAttribute *attr;
+
+                               attr = e_vcard_attribute_new (NULL, EVC_EMAIL);
+
+                               e_vcard_add_attribute_with_value (vcard, attr, str);
+                       }
+               }
+       }
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_parse_qp_email (const gchar *string,
+                        gchar **name,
+                        gchar **email)
+{
+       struct _camel_header_address *address;
+       gboolean res = FALSE;
+
+       address = camel_header_address_decode (string, "UTF-8");
+
+       if (address) {
+               /* report success only when we have filled both name and email address */
+               if (address->type == CAMEL_HEADER_ADDRESS_NAME && address->name && *address->name && 
address->v.addr && *address->v.addr) {
+                       *name = g_strdup (address->name);
+                       *email = g_strdup (address->v.addr);
+                       res = TRUE;
+               }
+
+               camel_header_address_unref (address);
+       }
+
+       if (!res) {
+               CamelInternetAddress *addr = camel_internet_address_new ();
+               const gchar *const_name = NULL, *const_email = NULL;
+
+               if (camel_address_unformat (CAMEL_ADDRESS (addr), string) == 1 &&
+                   camel_internet_address_get (addr, 0, &const_name, &const_email) &&
+                   const_name && *const_name && const_email && *const_email) {
+                       *name = g_strdup (const_name);
+                       *email = g_strdup (const_email);
+                       res = TRUE;
+               }
+
+               g_clear_object (&addr);
+       }
+
+       return res;
+}
+
+static gboolean
+ebb_o365_contact_add_emails (EBookBackendO365 *bbo365,
+                            EContact *new_contact,
+                            EContact *old_contact,
+                            EContactField field_id,
+                            JsonBuilder *builder,
+                            EO365Connection *cnc,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       GList *new_values, *old_values;
+
+       new_values = e_contact_get (new_contact, field_id);
+       old_values = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+       if (!ebb_o365_string_list_values_equal (new_values, old_values)) {
+               GList *link;
+
+               e_o365_contact_begin_email_addresses (builder);
+
+               for (link = new_values; link; link = g_list_next (link)) {
+                       const gchar *value = link->data;
+                       gchar *name = NULL, *address = NULL;
+
+                       if (ebb_o365_parse_qp_email (value, &name, &address))
+                               e_o365_add_email_address (builder, name, address);
+
+                       g_free (name);
+                       g_free (address);
+               }
+
+               e_o365_contact_end_email_addresses (builder);
+       }
+
+       g_list_free_full (new_values, g_free);
+       g_list_free_full (old_values, g_free);
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_add_file_as (EBookBackendO365 *bbo365,
+                             EContact *new_contact,
+                             EContact *old_contact,
+                             EContactField field_id,
+                             JsonBuilder *builder,
+                             EO365Connection *cnc,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       const gchar *new_value;
+
+       ebb_o365_contact_add_string_attribute (new_contact, old_contact, field_id, builder, 
e_o365_contact_add_file_as);
+
+       new_value = e_contact_get_const (new_contact, E_CONTACT_FILE_AS);
+
+       /* Set it always, to not be overwritten by server re-calculations on other property changes */
+       e_o365_contact_add_display_name (builder, new_value);
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_get_im_addresses (EBookBackendO365 *bbo365,
+                                  EO365Contact *o365_contact,
+                                  EContact *inout_contact,
+                                  EContactField field_id,
+                                  EO365Connection *cnc,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       JsonArray *values;
+
+       values = e_o365_contact_get_im_addresses (o365_contact);
+
+       if (values) {
+               EVCard *vcard = E_VCARD (inout_contact);
+               const gchar *field_name = e_contact_field_name (field_id);
+               guint ii, len;
+
+               len = json_array_get_length (values);
+
+               for (ii = 0; ii < len; ii++) {
+                       const gchar *str = json_array_get_string_element (values, ii);
+
+                       if (str && *str) {
+                               EVCardAttribute *attr;
+
+                               attr = e_vcard_attribute_new (NULL, field_name);
+
+                               e_vcard_add_attribute_with_value (vcard, attr, str);
+                       }
+               }
+       }
+
+       return TRUE;
+}
+
+static GSList * /* gchar * */
+ebb_o365_extract_im_addresses (EContact *contact)
+{
+       GSList *ims = NULL;
+       GList *attrs, *link;
+
+       if (!contact)
+               return NULL;
+
+       attrs = e_vcard_get_attributes (E_VCARD (contact));
+
+       for (link = attrs; link; link = g_list_next (link)) {
+               EVCardAttribute *attr = link->data;
+               const gchar *name;
+
+               if (!attr)
+                       continue;
+
+               name = e_vcard_attribute_get_name (attr);
+
+               if (!name || (
+                   g_ascii_strcasecmp (name, EVC_X_GOOGLE_TALK) != 0 &&
+                   g_ascii_strcasecmp (name, EVC_X_SKYPE) != 0 &&
+                   g_ascii_strcasecmp (name, EVC_X_GADUGADU) != 0 &&
+                   g_ascii_strcasecmp (name, EVC_X_AIM) != 0 &&
+                   g_ascii_strcasecmp (name, EVC_X_GROUPWISE) != 0 &&
+                   g_ascii_strcasecmp (name, EVC_X_JABBER) != 0 &&
+                   g_ascii_strcasecmp (name, EVC_X_YAHOO) != 0 &&
+                   g_ascii_strcasecmp (name, EVC_X_MSN) != 0 &&
+                   g_ascii_strcasecmp (name, EVC_X_ICQ) != 0))
+                       continue;
+
+               ims = g_slist_prepend (ims, e_vcard_attribute_get_value (attr));
+       }
+
+       return g_slist_reverse (ims);
+}
+
+static gboolean
+ebb_o365_contact_add_im_addresses (EBookBackendO365 *bbo365,
+                                  EContact *new_contact,
+                                  EContact *old_contact,
+                                  EContactField field_id,
+                                  JsonBuilder *builder,
+                                  EO365Connection *cnc,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       GSList *new_values, *old_values;
+
+       new_values = ebb_o365_extract_im_addresses (new_contact);
+       old_values = ebb_o365_extract_im_addresses (old_contact);
+
+       if (!ebb_o365_string_values_equal (new_values, old_values)) {
+               GSList *link;
+
+               e_o365_contact_begin_im_addresses (builder);
+
+               for (link = new_values; link; link = g_slist_next (link)) {
+                       const gchar *value = link->data;
+
+                       if (value && *value)
+                               e_o365_contact_add_im_address (builder, value);
+               }
+
+               e_o365_contact_end_im_addresses (builder);
+       }
+
+       g_slist_free_full (new_values, g_free);
+       g_slist_free_full (old_values, g_free);
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_get_middle_name (EBookBackendO365 *bbo365,
+                                 EO365Contact *o365_contact,
+                                 EContact *inout_contact,
+                                 EContactField field_id,
+                                 EO365Connection *cnc,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       const gchar *value;
+
+       value = e_o365_contact_get_middle_name (o365_contact);
+
+       if (value && *value) {
+               EContactName *name = e_contact_get (inout_contact, field_id);
+               gchar *prev;
+
+               if (!name)
+                       name = e_contact_name_new ();
+
+               prev = name->additional;
+               name->additional = (gchar *) value;
+
+               e_contact_set (inout_contact, field_id, name);
+
+               name->additional = prev;
+               e_contact_name_free (name);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_add_middle_name (EBookBackendO365 *bbo365,
+                                 EContact *new_contact,
+                                 EContact *old_contact,
+                                 EContactField field_id,
+                                 JsonBuilder *builder,
+                                 EO365Connection *cnc,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       EContactName *new_value, *old_value;
+
+       new_value = e_contact_get (new_contact, field_id);
+       old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+       if (!(new_value && old_value && g_strcmp0 (new_value->additional, old_value->additional) == 0))
+               e_o365_contact_add_middle_name (builder, new_value ? new_value->additional : NULL);
+
+       e_contact_name_free (new_value);
+       e_contact_name_free (old_value);
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_get_title (EBookBackendO365 *bbo365,
+                           EO365Contact *o365_contact,
+                           EContact *inout_contact,
+                           EContactField field_id,
+                           EO365Connection *cnc,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       const gchar *value;
+
+       value = e_o365_contact_get_middle_name (o365_contact);
+
+       if (value && *value) {
+               EContactName *name = e_contact_get (inout_contact, field_id);
+               gchar *prev;
+
+               if (!name)
+                       name = e_contact_name_new ();
+
+               prev = name->prefixes;
+               name->prefixes = (gchar *) value;
+
+               e_contact_set (inout_contact, field_id, name);
+
+               name->additional = prev;
+               e_contact_name_free (name);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_add_title (EBookBackendO365 *bbo365,
+                           EContact *new_contact,
+                           EContact *old_contact,
+                           EContactField field_id,
+                           JsonBuilder *builder,
+                           EO365Connection *cnc,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       EContactName *new_value, *old_value;
+
+       new_value = e_contact_get (new_contact, field_id);
+       old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+       if (!(new_value && old_value && g_strcmp0 (new_value->prefixes, old_value->prefixes) == 0))
+               e_o365_contact_add_middle_name (builder, new_value ? new_value->prefixes : NULL);
+
+       e_contact_name_free (new_value);
+       e_contact_name_free (old_value);
+
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_get_photo (EBookBackendO365 *bbo365,
+                           EO365Contact *o365_contact,
+                           EContact *inout_contact,
+                           EContactField field_id,
+                           EO365Connection *cnc,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       GByteArray *photo_data = NULL;
+       GError *local_error = NULL;
+
+       LOCK (bbo365);
+
+       if (e_o365_connection_get_contact_photo_sync (cnc, NULL, bbo365->priv->folder_id,
+               e_o365_contact_get_id (o365_contact), &photo_data, cancellable, &local_error) &&
+           photo_data && photo_data->len) {
+               EContactPhoto *photo;
+
+               photo = e_contact_photo_new ();
+               e_contact_photo_set_inlined (photo, photo_data->data, photo_data->len);
+               e_contact_photo_set_mime_type (photo, "image/jpeg");
+               e_contact_set (inout_contact, field_id, photo);
+               e_contact_photo_free (photo);
+       }
+
+       UNLOCK (bbo365);
+
+       if (photo_data)
+               g_byte_array_unref (photo_data);
+       g_clear_error (&local_error);
+
+       /* Even it could fail, ignore it and read as many contacts as possible, rather than stop on the first 
error */
+       return TRUE;
+}
+
+static gboolean
+ebb_o365_contact_photo_equal (EContactPhoto *photo1,
+                             EContactPhoto *photo2)
+{
+       const guchar *data1, *data2;
+       gsize len1 = 0, len2 = 0;
+
+       if (!photo1 && !photo2)
+               return TRUE;
+
+       if ((photo1 && !photo2) || (!photo1 && photo2))
+               return FALSE;
+
+       data1 = e_contact_photo_get_inlined (photo1, &len1);
+       data2 = e_contact_photo_get_inlined (photo2, &len2);
+
+       if (!data1 && !data2)
+               return TRUE;
+
+       return len1 == len2 &&
+               memcmp (data1, data2, len1) == 0;
+}
+
+static gboolean
+ebb_o365_contact_add_photo (EBookBackendO365 *bbo365,
+                           EContact *new_contact,
+                           EContact *old_contact,
+                           EContactField field_id,
+                           JsonBuilder *builder,
+                           EO365Connection *cnc,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       EContactPhoto *new_value, *old_value;
+
+       new_value = e_contact_get (new_contact, field_id);
+       old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+       if (!ebb_o365_contact_photo_equal (new_value, old_value)) {
+               GByteArray *jpeg_photo = NULL, tmp;
+               GError *local_error = NULL;
+
+               if (new_value) {
+                       gsize len = 0;
+
+                       tmp.data = (guchar *) e_contact_photo_get_inlined (new_value, &len);
+
+                       if (len && tmp.data) {
+                               tmp.len = len;
+                               jpeg_photo = &tmp;
+                       }
+               }
+
+               LOCK (bbo365);
+
+               if (!e_o365_connection_update_contact_photo_sync (cnc, NULL, bbo365->priv->folder_id,
+                       e_contact_get_const (new_contact, E_CONTACT_UID), jpeg_photo, cancellable, 
&local_error)) {
+                       g_warning ("%s: Failed to store photo for '%s': %s", G_STRFUNC, (const gchar *) 
e_contact_get_const (new_contact, E_CONTACT_UID),
+                               local_error ? local_error->message : "Unknown error");
+               }
+
+               UNLOCK (bbo365);
+
+               g_clear_error (&local_error);
+       }
+
+       e_contact_photo_free (new_value);
+       e_contact_photo_free (old_value);
+
+       return TRUE;
+}
+
+#define STRING_FIELD(fldid, getfn, addfn) { fldid, FALSE, getfn, NULL, addfn, NULL }
+#define COMPLEX_FIELD(fldid, getfn, addfn) { fldid, FALSE, NULL, getfn, NULL, addfn }
+#define COMPLEX_FIELD_2(fldid, getfn, addfn) { fldid, TRUE, NULL, getfn, NULL, addfn }
+#define COMPLEX_ADDFN(fldid, getfn, addfn) { fldid, FALSE, getfn, NULL, NULL, addfn }
+
+struct _mappings {
+       EContactField field_id;
+       gboolean add_in_second_go;
+       const gchar *   (* o365_get_func)       (EO365Contact *o365_contact);
+       gboolean        (* get_func)            (EBookBackendO365 *bbo365,
+                                                EO365Contact *o365_contact,
+                                                EContact *inout_contact,
+                                                EContactField field_id,
+                                                EO365Connection *cnc,
+                                                GCancellable *cancellable,
+                                                GError **error);
+       void            (* o365_add_func)       (JsonBuilder *builder,
+                                                const gchar *value);
+       gboolean        (* add_func)            (EBookBackendO365 *bbo365,
+                                                EContact *new_contact,
+                                                EContact *old_contact, /* nullable */
+                                                EContactField field_id,
+                                                JsonBuilder *builder,
+                                                EO365Connection *cnc,
+                                                GCancellable *cancellable,
+                                                GError **error);
+} mappings[] = {
+       STRING_FIELD    (E_CONTACT_UID,                 e_o365_contact_get_id,                  NULL),
+       COMPLEX_FIELD   (E_CONTACT_REV,                 ebb_o365_contact_get_rev,               NULL),
+       STRING_FIELD    (E_CONTACT_ASSISTANT,           e_o365_contact_get_assistant_name,      
e_o365_contact_add_assistant_name),
+       COMPLEX_FIELD   (E_CONTACT_BIRTH_DATE,          ebb_o365_contact_get_birthday,          
ebb_o365_contact_add_birthday),
+       COMPLEX_FIELD   (E_CONTACT_ADDRESS_WORK,        ebb_o365_contact_get_address,           
ebb_o365_contact_add_address),
+       STRING_FIELD    (E_CONTACT_HOMEPAGE_URL,        e_o365_contact_get_business_home_page,  
e_o365_contact_add_business_home_page),
+       COMPLEX_FIELD   (E_CONTACT_PHONE_BUSINESS,      ebb_o365_contact_get_phone,             
ebb_o365_contact_add_phone),
+       COMPLEX_FIELD   (E_CONTACT_CATEGORIES,          ebb_o365_contact_get_categories,        
ebb_o365_contact_add_categories),
+       STRING_FIELD    (E_CONTACT_ORG,                 e_o365_contact_get_company_name,        
e_o365_contact_add_company_name),
+       STRING_FIELD    (E_CONTACT_ORG_UNIT,            e_o365_contact_get_department,          
e_o365_contact_add_department),
+       COMPLEX_FIELD   (E_CONTACT_EMAIL,               ebb_o365_contact_get_emails,            
ebb_o365_contact_add_emails),
+       COMPLEX_ADDFN   (E_CONTACT_FILE_AS,             e_o365_contact_get_file_as,             
ebb_o365_contact_add_file_as),
+       /* STRING_FIELD (???,                           e_o365_contact_get_generation,          
e_o365_contact_add_generation), */
+       STRING_FIELD    (E_CONTACT_GIVEN_NAME,          e_o365_contact_get_given_name,          
e_o365_contact_add_given_name),
+       COMPLEX_FIELD   (E_CONTACT_ADDRESS_HOME,        ebb_o365_contact_get_address,           
ebb_o365_contact_add_address),
+       COMPLEX_FIELD   (E_CONTACT_PHONE_HOME,          ebb_o365_contact_get_phone,             
ebb_o365_contact_add_phone),
+       COMPLEX_FIELD   (E_CONTACT_IM_MSN,              ebb_o365_contact_get_im_addresses,      
ebb_o365_contact_add_im_addresses),
+       /* STRING_FIELD (???,                           e_o365_contact_get_initials,            
e_o365_contact_add_initials), */
+       STRING_FIELD    (E_CONTACT_TITLE,               e_o365_contact_get_job_title,           
e_o365_contact_add_job_title),
+       STRING_FIELD    (E_CONTACT_MANAGER,             e_o365_contact_get_manager,             
e_o365_contact_add_manager),
+       COMPLEX_FIELD   (E_CONTACT_NAME,                ebb_o365_contact_get_middle_name,       
ebb_o365_contact_add_middle_name),
+       STRING_FIELD    (E_CONTACT_PHONE_MOBILE,        e_o365_contact_get_mobile_phone,        
e_o365_contact_add_mobile_phone),
+       STRING_FIELD    (E_CONTACT_NICKNAME,            e_o365_contact_get_nick_name,           
e_o365_contact_add_nick_name),
+       STRING_FIELD    (E_CONTACT_OFFICE,              e_o365_contact_get_office_location,     
e_o365_contact_add_office_location),
+       COMPLEX_FIELD   (E_CONTACT_ADDRESS_OTHER,       ebb_o365_contact_get_address,           
ebb_o365_contact_add_address),
+       STRING_FIELD    (E_CONTACT_NOTE,                e_o365_contact_get_personal_notes,      
e_o365_contact_add_personal_notes),
+       STRING_FIELD    (E_CONTACT_ROLE,                e_o365_contact_get_profession,          
e_o365_contact_add_profession),
+       STRING_FIELD    (E_CONTACT_SPOUSE,              e_o365_contact_get_spouse_name,         
e_o365_contact_add_spouse_name),
+       STRING_FIELD    (E_CONTACT_FAMILY_NAME,         e_o365_contact_get_surname,             
e_o365_contact_add_surname),
+       COMPLEX_FIELD   (E_CONTACT_NAME,                ebb_o365_contact_get_title,             
ebb_o365_contact_add_title),
+       /* STRING_FIELD (???,                           e_o365_contact_get_yomi_company_name,   
e_o365_contact_add_yomi_company_name), */
+       /* STRING_FIELD (???,                           e_o365_contact_get_yomi_given_name,     
e_o365_contact_add_yomi_given_name), */
+       /* STRING_FIELD (???,                           e_o365_contact_get_yomi_surname,        
e_o365_contact_add_yomi_surname), */
+       COMPLEX_FIELD_2 (E_CONTACT_PHOTO,               ebb_o365_contact_get_photo,             
ebb_o365_contact_add_photo)
+};
+
+static gchar *
+ebb_o365_json_contact_to_vcard_string (EBookBackendO365 *bbo365,
+                                      EO365Contact *o365_contact,
+                                      EO365Connection *cnc,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+       EContact *contact;
+       gchar *object = NULL;
+       gint ii;
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (o365_contact != NULL, NULL);
+
+       contact = e_contact_new ();
+
+       for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
+               if (mappings[ii].o365_add_func) {
+                       ebb_o365_contact_get_string_attribute (o365_contact, contact, mappings[ii].field_id, 
mappings[ii].o365_get_func);
+               } else if (mappings[ii].get_func) {
+                       success = mappings[ii].get_func (bbo365, o365_contact, contact, 
mappings[ii].field_id, cnc, cancellable, error);
+               }
+       }
+
+       if (success)
+               object = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+
+       g_clear_object (&contact);
+
+       return object;
+}
+
+static JsonBuilder *
+ebb_o365_contact_to_json (EBookBackendO365 *bbo365,
+                         EContact *new_contact,
+                         EContact *old_contact, /* nullable */
+                         EO365Connection *cnc,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       JsonBuilder *builder;
+       gint ii;
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (new_contact != NULL, NULL);
+
+       builder = json_builder_new_immutable ();
+       e_o365_json_begin_object_member (builder, NULL);
+
+       for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
+               if (mappings[ii].o365_add_func) {
+                       ebb_o365_contact_add_string_attribute (new_contact, old_contact, 
mappings[ii].field_id, builder, mappings[ii].o365_add_func);
+               } else if (!mappings[ii].add_in_second_go && mappings[ii].add_func) {
+                       success = mappings[ii].add_func (bbo365, new_contact, old_contact, 
mappings[ii].field_id, builder, cnc, cancellable, error);
+               }
+       }
+
+       e_o365_json_end_object_member (builder);
+
+       if (!success)
+               g_clear_object (&builder);
+
+       return builder;
+}
+
 static void
 ebb_o365_convert_error_to_client_error (GError **perror)
 {
@@ -104,22 +1248,29 @@ ebb_o365_maybe_disconnect_sync (EBookBackendO365 *bbo365,
        }
 }
 
-static void
-ebb_o365_unset_connection (EBookBackendO365 *bbo365,
-                          gboolean is_disconnect)
+static gboolean
+ebb_o365_unset_connection_sync (EBookBackendO365 *bbo365,
+                               gboolean is_disconnect,
+                               GCancellable *cancellable,
+                               GError **error)
 {
-       g_return_if_fail (E_IS_BOOK_BACKEND_O365 (bbo365));
+       gboolean success = TRUE;
 
-       g_rec_mutex_lock (&bbo365->priv->property_lock);
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_O365 (bbo365), FALSE);
 
-       /*if (bbo365->priv->cnc) {
+       LOCK (bbo365);
+
+       if (bbo365->priv->cnc) {
                if (is_disconnect)
-                       e_o365_connection_set_disconnected_flag (bbo365->priv->cnc, TRUE);
+                       success = e_o365_connection_disconnect_sync (bbo365->priv->cnc, cancellable, error);
        }
 
-       g_clear_object (&bbo365->priv->cnc);*/
+       g_clear_object (&bbo365->priv->cnc);
+       g_clear_pointer (&bbo365->priv->folder_id, g_free);
+
+       UNLOCK (bbo365);
 
-       g_rec_mutex_unlock (&bbo365->priv->property_lock);
+       return success;
 }
 
 static gboolean
@@ -139,17 +1290,63 @@ ebb_o365_connect_sync (EBookMetaBackend *meta_backend,
 
        bbo365 = E_BOOK_BACKEND_O365 (meta_backend);
 
-       g_rec_mutex_lock (&bbo365->priv->property_lock);
+       LOCK (bbo365);
 
-       /*if (bbo365->priv->cnc)*/ {
-               g_rec_mutex_unlock (&bbo365->priv->property_lock);
+       if (bbo365->priv->cnc) {
+               UNLOCK (bbo365);
 
                *out_auth_result = E_SOURCE_AUTHENTICATION_ACCEPTED;
 
                return TRUE;
+       } else {
+               EBackend *backend;
+               ESourceRegistry *registry;
+               ESource *source;
+               EO365Connection *cnc;
+               ESourceO365Folder *o365_folder_extension;
+               CamelO365Settings *o365_settings;
+               gchar *folder_id;
+
+               backend = E_BACKEND (bbo365);
+               source = e_backend_get_source (backend);
+               registry = e_book_backend_get_registry (E_BOOK_BACKEND (bbo365));
+               o365_settings = camel_o365_settings_get_from_backend (backend, registry);
+               g_warn_if_fail (o365_settings != NULL);
+
+               o365_folder_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_O365_FOLDER);
+               folder_id = e_source_o365_folder_dup_id (o365_folder_extension);
+
+               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_certificate_pem, out_certificate_errors, cancellable, error);
+
+                       if (*out_auth_result == E_SOURCE_AUTHENTICATION_ACCEPTED) {
+                               bbo365->priv->cnc = g_object_ref (cnc);
+
+                               g_warn_if_fail (bbo365->priv->folder_id == NULL);
+
+                               g_free (bbo365->priv->folder_id);
+                               bbo365->priv->folder_id = folder_id;
+
+                               folder_id = NULL;
+                               success = TRUE;
+
+                               e_book_backend_set_writable (E_BOOK_BACKEND (bbo365), TRUE);
+                       }
+               } else {
+                       *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR;
+                       g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Folder ID is 
not set")));
+               }
+
+               g_clear_object (&cnc);
+               g_free (folder_id);
        }
 
-       g_rec_mutex_unlock (&bbo365->priv->property_lock);
+       UNLOCK (bbo365);
+
+       ebb_o365_convert_error_to_client_error (error);
 
        return success;
 }
@@ -161,7 +1358,71 @@ ebb_o365_disconnect_sync (EBookMetaBackend *meta_backend,
 {
        g_return_val_if_fail (E_IS_BOOK_BACKEND_O365 (meta_backend), FALSE);
 
-       ebb_o365_unset_connection (E_BOOK_BACKEND_O365 (meta_backend), TRUE);
+       return ebb_o365_unset_connection_sync (E_BOOK_BACKEND_O365 (meta_backend), TRUE, cancellable, error);
+}
+
+typedef struct _ObjectsDeltaData {
+       EBookBackendO365 *bbo365;
+       ECache *cache;
+       GSList **out_created_objects;
+       GSList **out_modified_objects;
+       GSList **out_removed_objects;
+} ObjectsDeltaData;
+
+static gboolean
+ebb_o365_get_objects_delta_cb (EO365Connection *cnc,
+                              const GSList *results, /* JsonObject * - the returned objects from the server 
*/
+                              gpointer user_data,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       ObjectsDeltaData *odd = user_data;
+       GSList *link;
+
+       g_return_val_if_fail (odd != NULL, FALSE);
+
+       for (link = (GSList *) results; link && !g_cancellable_is_cancelled (cancellable); link = 
g_slist_next (link)) {
+               EO365Contact *contact = link->data;
+               const gchar *id;
+
+               if (!contact)
+                       continue;
+
+               id = e_o365_contact_get_id (contact);
+
+               if (!id)
+                       continue;
+
+               if (e_o365_delta_is_removed_object (contact)) {
+                       *(odd->out_removed_objects) = g_slist_prepend (*(odd->out_removed_objects),
+                               e_book_meta_backend_info_new (id, NULL, NULL, NULL));
+               } else {
+                       GSList **out_slist;
+                       gchar *object;
+
+                       if (e_cache_contains (odd->cache, id, E_CACHE_INCLUDE_DELETED))
+                               out_slist = odd->out_modified_objects;
+                       else
+                               out_slist = odd->out_created_objects;
+
+                       object = ebb_o365_json_contact_to_vcard_string (odd->bbo365, contact, cnc, 
cancellable, error);
+
+                       if (!g_cancellable_is_cancelled (cancellable))
+                               g_warn_if_fail (object != NULL);
+
+                       if (object) {
+                               EBookMetaBackendInfo *nfo;
+
+                               nfo = e_book_meta_backend_info_new (id,
+                                       e_o365_contact_get_change_key (contact),
+                                       object, NULL);
+
+                               nfo->extra = object; /* assumes ownership, to avoid unnecessary re-allocation 
*/
+
+                               *out_slist = g_slist_prepend (*out_slist, nfo);
+                       }
+               }
+       }
 
        return TRUE;
 }
@@ -180,8 +1441,9 @@ ebb_o365_get_changes_sync (EBookMetaBackend *meta_backend,
 {
        EBookBackendO365 *bbo365;
        EBookCache *book_cache;
-       gboolean success = TRUE;
-       /*GError *local_error = NULL;*/
+       ObjectsDeltaData odd;
+       gboolean success;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_BOOK_BACKEND_O365 (meta_backend), FALSE);
        g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
@@ -199,9 +1461,48 @@ ebb_o365_get_changes_sync (EBookMetaBackend *meta_backend,
        book_cache = e_book_meta_backend_ref_cache (meta_backend);
        g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
 
-       g_rec_mutex_lock (&bbo365->priv->property_lock);
+       odd.bbo365 = bbo365;
+       odd.cache = E_CACHE (book_cache);
+       odd.out_created_objects = out_created_objects;
+       odd.out_modified_objects = out_modified_objects;
+       odd.out_removed_objects = out_removed_objects;
+
+       LOCK (bbo365);
+
+       success = e_o365_connection_get_objects_delta_sync (bbo365->priv->cnc, NULL,
+               E_O365_FOLDER_KIND_CONTACTS, bbo365->priv->folder_id, NULL, last_sync_tag, 0,
+               ebb_o365_get_objects_delta_cb, &odd,
+               out_new_sync_tag, cancellable, &local_error);
+
+       if (e_o365_connection_util_delta_token_failed (local_error)) {
+               GSList *known_uids = NULL, *link;
 
-       g_rec_mutex_unlock (&bbo365->priv->property_lock);
+               g_clear_error (&local_error);
+
+               if (e_book_cache_search_uids (book_cache, NULL, &known_uids, cancellable, error)) {
+                       for (link = known_uids; link; link = g_slist_next (link)) {
+                               const gchar *uid = link->data;
+
+                               if (uid) {
+                                       *out_removed_objects = g_slist_prepend (*out_removed_objects,
+                                               e_book_meta_backend_info_new (uid, NULL, NULL, NULL));
+                               }
+                       }
+               }
+
+               e_cache_remove_all (E_CACHE (book_cache), cancellable, NULL);
+
+               g_slist_free_full (known_uids, g_free);
+
+               success = e_o365_connection_get_objects_delta_sync (bbo365->priv->cnc, NULL,
+                       E_O365_FOLDER_KIND_CONTACTS, bbo365->priv->folder_id, NULL, NULL, 0,
+                       ebb_o365_get_objects_delta_cb, &odd,
+                       out_new_sync_tag, cancellable, &local_error);
+       } else if (local_error) {
+               g_propagate_error (error, local_error);
+       }
+
+       UNLOCK (bbo365);
 
        ebb_o365_convert_error_to_client_error (error);
        ebb_o365_maybe_disconnect_sync (bbo365, error, cancellable);
@@ -229,9 +1530,9 @@ ebb_o365_load_contact_sync (EBookMetaBackend *meta_backend,
 
        bbo365 = E_BOOK_BACKEND_O365 (meta_backend);
 
-       g_rec_mutex_lock (&bbo365->priv->property_lock);
+       LOCK (bbo365);
 
-       g_rec_mutex_unlock (&bbo365->priv->property_lock);
+       UNLOCK (bbo365);
 
        ebb_o365_convert_error_to_client_error (error);
        ebb_o365_maybe_disconnect_sync (bbo365, error, cancellable);
@@ -252,6 +1553,7 @@ ebb_o365_save_contact_sync (EBookMetaBackend *meta_backend,
                            GError **error)
 {
        EBookBackendO365 *bbo365;
+       EContact *tmp_contact = NULL;
        gboolean success = FALSE;
 
        g_return_val_if_fail (E_IS_BOOK_BACKEND_O365 (meta_backend), FALSE);
@@ -261,13 +1563,22 @@ ebb_o365_save_contact_sync (EBookMetaBackend *meta_backend,
 
        bbo365 = E_BOOK_BACKEND_O365 (meta_backend);
 
-       g_rec_mutex_lock (&bbo365->priv->property_lock);
+       LOCK (bbo365);
+
+       if (e_vcard_get_attribute (E_VCARD (contact), EVC_PHOTO)) {
+               tmp_contact = e_contact_duplicate (contact);
+               contact = tmp_contact;
+
+               e_contact_inline_local_photos (contact, NULL);
+       }
 
-       g_rec_mutex_unlock (&bbo365->priv->property_lock);
+       UNLOCK (bbo365);
 
        ebb_o365_convert_error_to_client_error (error);
        ebb_o365_maybe_disconnect_sync (bbo365, error, cancellable);
 
+       g_clear_object (&tmp_contact);
+
        return success;
 }
 
@@ -288,9 +1599,9 @@ ebb_o365_remove_contact_sync (EBookMetaBackend *meta_backend,
 
        bbo365 = E_BOOK_BACKEND_O365 (meta_backend);
 
-       g_rec_mutex_lock (&bbo365->priv->property_lock);
+       LOCK (bbo365);
 
-       g_rec_mutex_unlock (&bbo365->priv->property_lock);
+       UNLOCK (bbo365);
 
        ebb_o365_convert_error_to_client_error (error);
        ebb_o365_maybe_disconnect_sync (bbo365, error, cancellable);
@@ -355,41 +1666,24 @@ ebb_o365_get_backend_property (EBookBackend *book_backend,
        } else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) {
                GString *buffer;
                gchar *fields;
-               /*gint ii;*/
+               gint ii;
 
                buffer = g_string_sized_new (1024);
 
-               /*for (ii = 0; ii < G_N_ELEMENTS (mappings); ii++) {
-                       if (mappings[ii].element_type != ELEMENT_TYPE_SIMPLE)
-                               continue;
-
+               for (ii = 0; ii < G_N_ELEMENTS (mappings); ii++) {
                        if (buffer->len > 0)
                                g_string_append_c (buffer, ',');
+
                        g_string_append (buffer, e_contact_field_name (mappings[ii].field_id));
                }
 
-               for (ii = 0; ii < G_N_ELEMENTS (phone_field_map); ii++) {
-                       if (buffer->len > 0)
-                               g_string_append_c (buffer, ',');
-                       g_string_append (buffer, e_contact_field_name (phone_field_map[ii].field));
-               }*/
-
                fields = g_strjoin (
                        ",",
                        buffer->str,
-                       e_contact_field_name (E_CONTACT_FULL_NAME),
-                       e_contact_field_name (E_CONTACT_NICKNAME),
-                       e_contact_field_name (E_CONTACT_FAMILY_NAME),
                        e_contact_field_name (E_CONTACT_EMAIL_1),
                        e_contact_field_name (E_CONTACT_EMAIL_2),
                        e_contact_field_name (E_CONTACT_EMAIL_3),
-                       e_contact_field_name (E_CONTACT_ADDRESS_WORK),
-                       e_contact_field_name (E_CONTACT_ADDRESS_HOME),
-                       e_contact_field_name (E_CONTACT_ADDRESS_OTHER),
-                       e_contact_field_name (E_CONTACT_ANNIVERSARY),
-                       e_contact_field_name (E_CONTACT_BIRTH_DATE),
-                       e_contact_field_name (E_CONTACT_NOTE),
-                       e_contact_field_name (E_CONTACT_PHOTO),
+                       e_contact_field_name (E_CONTACT_EMAIL_4),
                        NULL);
 
                g_string_free (buffer, TRUE);
@@ -425,7 +1719,7 @@ e_book_backend_o365_dispose (GObject *object)
 {
        EBookBackendO365 *bbo365 = E_BOOK_BACKEND_O365 (object);
 
-       ebb_o365_unset_connection (bbo365, FALSE);
+       ebb_o365_unset_connection_sync (bbo365, FALSE, NULL, NULL);
 
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (e_book_backend_o365_parent_class)->dispose (object);
diff --git a/src/Office365/camel/camel-o365-folder.c b/src/Office365/camel/camel-o365-folder.c
index b0c89027..48ea5b94 100644
--- a/src/Office365/camel/camel-o365-folder.c
+++ b/src/Office365/camel/camel-o365-folder.c
@@ -1045,11 +1045,11 @@ o365_folder_refresh_info_sync (CamelFolder *folder,
        sdd.changes = NULL;
        sdd.removed_uids = NULL;
 
-       success = e_o365_connection_get_mail_messages_delta_sync (cnc, NULL, folder_id, 
O365_FETCH_SUMMARY_PROPERTIES,
+       success = e_o365_connection_get_objects_delta_sync (cnc, NULL, E_O365_FOLDER_KIND_MAIL, folder_id, 
O365_FETCH_SUMMARY_PROPERTIES,
                curr_delta_link, 0, o365_folder_got_summary_messages_cb, &sdd,
                &new_delta_link, cancellable, &local_error);
 
-       if (curr_delta_link && g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED)) {
+       if (curr_delta_link && e_o365_connection_util_delta_token_failed (local_error)) {
                g_clear_error (&local_error);
                g_clear_pointer (&curr_delta_link, g_free);
 
@@ -1057,7 +1057,7 @@ o365_folder_refresh_info_sync (CamelFolder *folder,
 
                o365_folder_forget_all_mails (o365_folder);
 
-               success = e_o365_connection_get_mail_messages_delta_sync (cnc, NULL, folder_id, 
O365_FETCH_SUMMARY_PROPERTIES,
+               success = e_o365_connection_get_objects_delta_sync (cnc, NULL, E_O365_FOLDER_KIND_MAIL, 
folder_id, O365_FETCH_SUMMARY_PROPERTIES,
                        NULL, 0, o365_folder_got_summary_messages_cb, &sdd,
                        &new_delta_link, cancellable, &local_error);
        }
diff --git a/src/Office365/camel/camel-o365-store.c b/src/Office365/camel/camel-o365-store.c
index a5c4af67..4142b23a 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, cancellable, error)) {
+       switch (e_o365_connection_authenticate_sync (cnc, NULL, E_O365_FOLDER_KIND_MAIL, NULL, NULL, NULL, 
cancellable, error)) {
        case E_SOURCE_AUTHENTICATION_ERROR:
        case E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED:
        default:
@@ -1269,9 +1269,7 @@ o365_store_get_folder_info_sync (CamelStore *store,
                                success = e_o365_connection_get_folders_delta_sync (cnc, NULL, 
E_O365_FOLDER_KIND_MAIL, NULL, old_delta_link, 0,
                                        camel_o365_got_folders_delta_cb, &fdd, &new_delta_link, cancellable, 
&local_error);
 
-                               if (old_delta_link && *old_delta_link && (
-                                   g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) 
||
-                                   g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_BAD_REQUEST))) 
{
+                               if (old_delta_link && *old_delta_link && 
e_o365_connection_util_delta_token_failed (local_error)) {
                                        g_clear_pointer (&old_delta_link, g_free);
                                        g_clear_error (&local_error);
 
diff --git a/src/Office365/camel/camel-o365-transport.c b/src/Office365/camel/camel-o365-transport.c
index 6e8b0071..7149f975 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, cancellable, error)) {
+       switch (e_o365_connection_authenticate_sync (cnc, NULL, E_O365_FOLDER_KIND_MAIL, 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 ef5be174..0f05eef4 100644
--- a/src/Office365/common/e-o365-connection.c
+++ b/src/Office365/common/e-o365-connection.c
@@ -718,6 +718,13 @@ e_o365_connection_init (EO365Connection *cnc)
                G_BINDING_DEFAULT);
 }
 
+gboolean
+e_o365_connection_util_delta_token_failed (const GError *error)
+{
+       return g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
+              g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_BAD_REQUEST);
+}
+
 EO365Connection *
 e_o365_connection_new (ESource *source,
                       CamelO365Settings *settings)
@@ -1362,6 +1369,39 @@ e_o365_read_no_response_cb (EO365Connection *cnc,
        return TRUE;
 }
 
+static gboolean
+e_o365_read_to_byte_array_cb (EO365Connection *cnc,
+                             SoupMessage *message,
+                             GInputStream *raw_data_stream,
+                             gpointer user_data,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       GByteArray **out_byte_array = user_data;
+       gchar buffer[4096];
+       gssize n_read;
+
+       g_return_val_if_fail (message != NULL, FALSE);
+       g_return_val_if_fail (out_byte_array != NULL, FALSE);
+
+       if (!*out_byte_array) {
+               goffset content_length;
+
+               content_length = soup_message_headers_get_content_length (message->response_headers);
+
+               if (!content_length || content_length > 65536)
+                       content_length = 65535;
+
+               *out_byte_array = g_byte_array_sized_new (content_length);
+       }
+
+       while (n_read = g_input_stream_read (raw_data_stream, buffer, sizeof (buffer), cancellable, error), 
n_read > 0) {
+               g_byte_array_append (*out_byte_array, (const guint8 *) buffer, n_read);
+       }
+
+       return !n_read;
+}
+
 typedef struct _EO365ResponseData {
        EO365ConnectionJsonFunc json_func;
        gpointer func_user_data;
@@ -1476,6 +1516,10 @@ o365_connection_new_soup_message (const gchar *method,
                soup_message_headers_append (message->request_headers, "Connection", "Close");
                soup_message_headers_append (message->request_headers, "User-Agent", "Evolution-O365/" 
VERSION);
 
+               /* Disable caching for proxies (RFC 4918, section 10.4.5) */
+               soup_message_headers_append (message->request_headers, "Cache-Control", "no-cache");
+               soup_message_headers_append (message->request_headers, "Pragma", "no-cache");
+
                if ((csm_flags & CSM_DISABLE_RESPONSE) != 0)
                        soup_message_headers_append (message->request_headers, "Prefer", "return=minimal");
        } else {
@@ -1511,26 +1555,52 @@ e_o365_connection_get_ssl_error_details (EO365Connection *cnc,
 
 ESourceAuthenticationResult
 e_o365_connection_authenticate_sync (EO365Connection *cnc,
+                                    const gchar *user_override,
+                                    EO365FolderKind kind,
+                                    const gchar *folder_id,
+                                    gchar **out_certificate_pem,
+                                    GTlsCertificateFlags *out_certificate_errors,
                                     GCancellable *cancellable,
                                     GError **error)
 {
        ESourceAuthenticationResult result = E_SOURCE_AUTHENTICATION_ERROR;
-       EO365ResponseData rd;
        SoupMessage *message;
+       JsonObject *object = NULL;
        gchar *uri;
+       const gchar *resource = NULL;
        gboolean success;
-       GSList *folders = NULL;
        GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), result);
 
        /* Just pick an inexpensive operation */
-       uri = e_o365_connection_construct_uri (cnc, TRUE, NULL, E_O365_API_V1_0, NULL,
-               "mailFolders",
-               NULL,
+       switch (kind) {
+       case E_O365_FOLDER_KIND_UNKNOWN:
+       case E_O365_FOLDER_KIND_MAIL:
+               resource = "mailFolders";
+
+               if (!folder_id || !*folder_id)
+                       folder_id = "inbox";
+               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";
+               break;
+       }
+
+       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+               resource,
+               folder_id,
                NULL,
                "$select", "displayName",
-               "$top", "1",
                NULL);
 
        message = o365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
@@ -1543,12 +1613,7 @@ e_o365_connection_authenticate_sync (EO365Connection *cnc,
 
        g_free (uri);
 
-       memset (&rd, 0, sizeof (EO365ResponseData));
-
-       rd.read_only_once = TRUE;
-       rd.out_items = &folders;
-
-       success = o365_connection_send_request_sync (cnc, message, e_o365_read_valued_response_cb, NULL, &rd, 
cancellable, &local_error);
+       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;
@@ -1558,6 +1623,9 @@ e_o365_connection_authenticate_sync (EO365Connection *cnc,
                        local_error->code = G_IO_ERROR_CANCELLED;
                } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED)) {
                        result = E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED;
+
+                       if (out_certificate_pem || out_certificate_errors)
+                               e_o365_connection_get_ssl_error_details (cnc, out_certificate_pem, 
out_certificate_errors);
                } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED)) {
                        ESoupAuthBearer *bearer;
 
@@ -1588,7 +1656,9 @@ e_o365_connection_authenticate_sync (EO365Connection *cnc,
                }
        }
 
-       g_slist_free_full (folders, (GDestroyNotify) json_object_unref);
+       if (object)
+               json_object_unref (object);
+
        g_clear_object (&message);
        g_clear_error (&local_error);
 
@@ -1756,7 +1826,7 @@ e_o365_connection_set_json_body (SoupMessage *message,
 
        data = json_generator_to_data (generator, &data_length);
 
-       soup_message_headers_append (message->request_headers, "Content-Type", "application/json");
+       soup_message_headers_set_content_type (message->request_headers, "application/json", NULL);
 
        if (data)
                soup_message_body_append_take (message->request_body, (guchar *) data, data_length);
@@ -2510,20 +2580,22 @@ e_o365_connection_rename_mail_folder_sync (EO365Connection *cnc,
        return success;
 }
 
-/* https://docs.microsoft.com/en-us/graph/api/message-delta?view=graph-rest-1.0&tabs=http */
+/* https://docs.microsoft.com/en-us/graph/api/message-delta?view=graph-rest-1.0&tabs=http
+   https://docs.microsoft.com/en-us/graph/api/contact-delta?view=graph-rest-1.0&tabs=http */
 
 gboolean
-e_o365_connection_get_mail_messages_delta_sync (EO365Connection *cnc,
-                                               const gchar *user_override, /* for which user, NULL to use 
the account user */
-                                               const gchar *folder_id, /* folder ID to get delta messages in 
*/
-                                               const gchar *select, /* properties to select, nullable */
-                                               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 */
-                                               gpointer func_user_data, /* user data passed into the 'func' 
*/
-                                               gchar **out_delta_link,
-                                               GCancellable *cancellable,
-                                               GError **error)
+e_o365_connection_get_objects_delta_sync (EO365Connection *cnc,
+                                         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 *delta_link, /* previous delta link */
+                                         guint max_page_size, /* 0 for default by the server */
+                                         EO365ConnectionJsonFunc func, /* function to call with each result 
set */
+                                         gpointer func_user_data, /* user data passed into the 'func' */
+                                         gchar **out_delta_link,
+                                         GCancellable *cancellable,
+                                         GError **error)
 {
        EO365ResponseData rd;
        SoupMessage *message = NULL;
@@ -2538,12 +2610,29 @@ e_o365_connection_get_mail_messages_delta_sync (EO365Connection *cnc,
                message = o365_connection_new_soup_message (SOUP_METHOD_GET, delta_link, CSM_DEFAULT, NULL);
 
        if (!message) {
+               const gchar *kind_str = NULL, *kind_path_str = NULL;
                gchar *uri;
 
+               switch (kind) {
+               case E_O365_FOLDER_KIND_CONTACTS:
+                       kind_str = "contactFolders";
+                       kind_path_str = "contacts";
+                       break;
+               case E_O365_FOLDER_KIND_MAIL:
+                       kind_str = "mailFolders";
+                       kind_path_str = "messages";
+                       break;
+               default:
+                       g_warn_if_reached ();
+                       break;
+               }
+
+               g_return_val_if_fail (kind_str != NULL && kind_path_str != NULL, FALSE);
+
                uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
-                       "mailFolders",
+                       kind_str,
                        folder_id,
-                       "messages",
+                       kind_path_str,
                        "", "delta",
                        "$select", select,
                        NULL);
@@ -2604,11 +2693,9 @@ e_o365_connection_get_mail_message_sync (EO365Connection *cnc,
        g_return_val_if_fail (func != NULL, FALSE);
 
        uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
-               /*"mailFolders",
-               folder_id,*/
                "messages",
-               "", message_id,
-               "", "$value",
+               message_id,
+               "$value",
                NULL);
 
        message = o365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
@@ -3193,3 +3280,98 @@ e_o365_connection_get_contacts_folder_sync (EO365Connection *cnc,
 
        return success;
 }
+
+/* https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0 */
+
+gboolean
+e_o365_connection_get_contact_photo_sync (EO365Connection *cnc,
+                                         const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                         const gchar *folder_id,
+                                         const gchar *contact_id,
+                                         GByteArray **out_photo,
+                                         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 (folder_id != NULL, FALSE);
+       g_return_val_if_fail (contact_id != NULL, FALSE);
+       g_return_val_if_fail (out_photo != NULL, FALSE);
+
+       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+               "contactFolders",
+               folder_id,
+               "contacts",
+               "", contact_id,
+               "", "photo",
+               "", "$value",
+               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, NULL, e_o365_read_to_byte_array_cb, 
out_photo, cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/profilephoto-update?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_update_contact_photo_sync (EO365Connection *cnc,
+                                            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 */
+                                            GCancellable *cancellable,
+                                            GError **error)
+{
+       SoupMessage *message;
+       gboolean success;
+       gchar *uri;
+
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+
+       uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+               "contactFolders",
+               folder_id,
+               "contacts",
+               "", contact_id,
+               "", "photo",
+               "", "$value",
+               NULL);
+
+       message = o365_connection_new_soup_message (SOUP_METHOD_PUT, uri, CSM_DEFAULT, error);
+
+       if (!message) {
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       g_free (uri);
+
+       soup_message_headers_set_content_type (message->request_headers, "image/jpeg", NULL);
+       soup_message_headers_set_content_length (message->request_headers, jpeg_photo ? jpeg_photo->len : 0);
+
+       if (jpeg_photo)
+               soup_message_body_append (message->request_body, SOUP_MEMORY_STATIC, jpeg_photo->data, 
jpeg_photo->len);
+
+       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 a19fbb20..f49529eb 100644
--- a/src/Office365/common/e-o365-connection.h
+++ b/src/Office365/common/e-o365-connection.h
@@ -85,6 +85,9 @@ struct _EO365ConnectionClass {
        GObjectClass parent_class;
 };
 
+gboolean       e_o365_connection_util_delta_token_failed
+                                               (const GError *error);
+
 GType          e_o365_connection_get_type      (void) G_GNUC_CONST;
 
 EO365Connection *
@@ -126,6 +129,11 @@ gboolean   e_o365_connection_get_ssl_error_details
 ESourceAuthenticationResult
                e_o365_connection_authenticate_sync
                                                (EO365Connection *cnc,
+                                                const gchar *user_override,
+                                                EO365FolderKind kind,
+                                                const gchar *folder_id,
+                                                gchar **out_certificate_pem,
+                                                GTlsCertificateFlags *out_certificate_errors,
                                                 GCancellable *cancellable,
                                                 GError **error);
 gboolean       e_o365_connection_disconnect_sync
@@ -216,9 +224,10 @@ gboolean   e_o365_connection_rename_mail_folder_sync
                                                 EO365MailFolder **out_mail_folder,
                                                 GCancellable *cancellable,
                                                 GError **error);
-gboolean       e_o365_connection_get_mail_messages_delta_sync
+gboolean       e_o365_connection_get_objects_delta_sync
                                                (EO365Connection *cnc,
                                                 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 *delta_link, /* previous delta link */
@@ -300,6 +309,22 @@ gboolean   e_o365_connection_get_contacts_folder_sync
                                                 EO365Folder **out_folder,
                                                 GCancellable *cancellable,
                                                 GError **error);
+gboolean       e_o365_connection_get_contact_photo_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *folder_id,
+                                                const gchar *contact_id,
+                                                GByteArray **out_photo,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_update_contact_photo_sync
+                                               (EO365Connection *cnc,
+                                                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 */
+                                                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 a3cdb79f..bb509252 100644
--- a/src/Office365/common/e-o365-json-utils.c
+++ b/src/Office365/common/e-o365-json-utils.c
@@ -245,6 +245,30 @@ e_o365_json_add_string_member (JsonBuilder *builder,
        json_builder_add_string_value (builder, value ? value : "");
 }
 
+void
+e_o365_json_add_nonempty_string_member (JsonBuilder *builder,
+                                       const gchar *member_name,
+                                       const gchar *value)
+{
+       g_return_if_fail (member_name && *member_name);
+
+       if (value && *value)
+               e_o365_json_add_string_member (builder, member_name, value);
+}
+
+void
+e_o365_json_add_nonempty_or_null_string_member (JsonBuilder *builder,
+                                               const gchar *member_name,
+                                               const gchar *value)
+{
+       g_return_if_fail (member_name && *member_name);
+
+       if (value && *value)
+               e_o365_json_add_string_member (builder, member_name, value);
+       else
+               e_o365_json_add_null_member (builder, member_name);
+}
+
 time_t
 e_o365_get_date_time_offset_member (JsonObject *object,
                                    const gchar *member_name)
@@ -276,7 +300,7 @@ e_o365_add_date_time_offset_member (JsonBuilder *builder,
        GDateTime *dt;
        gchar *value_str;
 
-       if ((time_t) value <= 0) {
+       if (value <= (time_t) 0) {
                e_o365_json_add_null_member (builder, member_name);
                return;
        }
@@ -443,11 +467,8 @@ e_o365_add_recipient (JsonBuilder *builder,
        e_o365_json_begin_object_member (builder, member_name);
        e_o365_json_begin_object_member (builder, "emailAddress");
 
-       if (name && *name)
-               e_o365_json_add_string_member (builder, "name", name);
-
-       if (address && *address)
-               e_o365_json_add_string_member (builder, "address", address);
+       e_o365_json_add_nonempty_string_member (builder, "name", name);
+       e_o365_json_add_nonempty_string_member (builder, "address", address);
 
        e_o365_json_end_object_member (builder); /* emailAddress */
        e_o365_json_end_object_member (builder); /* member_name */
@@ -483,9 +504,7 @@ e_o365_add_date_time (JsonBuilder *builder,
        e_o365_json_begin_object_member (builder, member_name);
 
        e_o365_add_date_time_offset_member (builder, "dateTime", date_time);
-
-       if (zone && *zone)
-               e_o365_json_add_string_member (builder, "timeZone", zone);
+       e_o365_json_add_nonempty_string_member (builder, "timeZone", zone);
 
        e_o365_json_end_object_member (builder);
 }
@@ -906,8 +925,7 @@ void
 e_o365_mail_message_add_internet_message_id (JsonBuilder *builder,
                                             const gchar *message_id)
 {
-       if (message_id && *message_id)
-               e_o365_json_add_string_member (builder, "internetMessageId", message_id);
+       e_o365_json_add_nonempty_string_member (builder, "internetMessageId", message_id);
 }
 
 gboolean
@@ -1037,8 +1055,7 @@ void
 e_o365_mail_message_add_subject (JsonBuilder *builder,
                                 const gchar *subject)
 {
-       if (subject)
-               e_o365_json_add_string_member (builder, "subject", subject);
+       e_o365_json_add_nonempty_string_member (builder, "subject", subject);
 }
 
 JsonArray * /* EO365Recipient * */
@@ -1213,3 +1230,638 @@ e_o365_file_attachment_add_content_id (JsonBuilder *builder,
 {
        e_o365_json_add_string_member (builder, "contentId", value);
 }
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/emailaddress?view=graph-rest-1.0 */
+
+const gchar *
+e_o365_email_address_get_name (EO365EmailAddress *email)
+{
+       return e_o365_json_get_string_member (email, "name", NULL);
+}
+
+const gchar *
+e_o365_email_address_get_address (EO365EmailAddress *email)
+{
+       return e_o365_json_get_string_member (email, "address", NULL);
+}
+
+void
+e_o365_add_email_address (JsonBuilder *builder,
+                         const gchar *name,
+                         const gchar *address)
+{
+       g_return_if_fail ((name && *name) || (address && *address));
+
+       e_o365_json_begin_object_member (builder, NULL);
+
+       e_o365_json_add_nonempty_string_member (builder, "name", name);
+       e_o365_json_add_nonempty_string_member (builder, "address", address);
+
+       e_o365_json_end_object_member (builder); /* unnamed object */
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/physicaladdress?view=graph-rest-1.0 */
+
+const gchar *
+e_o365_physical_address_get_city (EO365PhysicalAddress *address)
+{
+       return e_o365_json_get_string_member (address, "city", NULL);
+}
+
+const gchar *
+e_o365_physical_address_get_country_or_region (EO365PhysicalAddress *address)
+{
+       return e_o365_json_get_string_member (address, "countryOrRegion", NULL);
+}
+
+const gchar *
+e_o365_physical_address_get_postal_code (EO365PhysicalAddress *address)
+{
+       return e_o365_json_get_string_member (address, "postalCode", NULL);
+}
+
+const gchar *
+e_o365_physical_address_get_state (EO365PhysicalAddress *address)
+{
+       return e_o365_json_get_string_member (address, "state", NULL);
+}
+
+const gchar *
+e_o365_physical_address_get_street (EO365PhysicalAddress *address)
+{
+       return e_o365_json_get_string_member (address, "street", NULL);
+}
+
+void
+e_o365_add_physical_address (JsonBuilder *builder,
+                            const gchar *member_name,
+                            const gchar *city,
+                            const gchar *country_or_region,
+                            const gchar *postal_code,
+                            const gchar *state,
+                            const gchar *street)
+{
+       if ((city && *city) ||
+           (country_or_region && *country_or_region) ||
+           (postal_code && *postal_code) ||
+           (state && *state) ||
+           (street && *street)) {
+               e_o365_json_begin_object_member (builder, member_name);
+               e_o365_json_add_nonempty_string_member (builder, "city", city);
+               e_o365_json_add_nonempty_string_member (builder, "countryOrRegion", country_or_region);
+               e_o365_json_add_nonempty_string_member (builder, "postalCode", postal_code);
+               e_o365_json_add_nonempty_string_member (builder, "state", state);
+               e_o365_json_add_nonempty_string_member (builder, "street", street);
+               e_o365_json_end_object_member (builder);
+       } else {
+               e_o365_json_add_null_member (builder, member_name);
+       }
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/contact?view=graph-rest-1.0 */
+
+const gchar *
+e_o365_contact_get_id (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "id", NULL);
+}
+
+const gchar *
+e_o365_contact_get_parent_folder_id (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "parentFolderId", NULL);
+}
+
+const gchar *
+e_o365_contact_get_change_key (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "changeKey", NULL);
+}
+
+time_t
+e_o365_contact_get_created_date_time (EO365Contact *contact)
+{
+       return e_o365_get_date_time_offset_member (contact, "createdDateTime");
+}
+
+time_t
+e_o365_contact_get_last_modified_date_time (EO365Contact *contact)
+{
+       return e_o365_get_date_time_offset_member (contact, "lastModifiedDateTime");
+}
+
+const gchar *
+e_o365_contact_get_assistant_name (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "assistantName", NULL);
+}
+
+void
+e_o365_contact_add_assistant_name (JsonBuilder *builder,
+                                  const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "assistantName", value);
+}
+
+time_t
+e_o365_contact_get_birthday (EO365Contact *contact)
+{
+       return e_o365_get_date_time_offset_member (contact, "birthday");
+}
+
+void
+e_o365_contact_add_birthday (JsonBuilder *builder,
+                            time_t value)
+{
+       e_o365_add_date_time_offset_member (builder, "birthday", value);
+}
+
+EO365PhysicalAddress *
+e_o365_contact_get_business_address (EO365Contact *contact)
+{
+       return e_o365_json_get_object_member (contact, "businessAddress");
+}
+
+void
+e_o365_contact_add_business_address (JsonBuilder *builder,
+                                    const gchar *city,
+                                    const gchar *country_or_region,
+                                    const gchar *postal_code,
+                                    const gchar *state,
+                                    const gchar *street)
+{
+       e_o365_add_physical_address (builder, "businessAddress", city, country_or_region, postal_code, state, 
street);
+}
+
+const gchar *
+e_o365_contact_get_business_home_page (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "businessHomePage", NULL);
+}
+
+void
+e_o365_contact_add_business_home_page (JsonBuilder *builder,
+                                      const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "businessHomePage", value);
+}
+
+JsonArray * /* const gchar * */
+e_o365_contact_get_business_phones (EO365Contact *contact)
+{
+       return e_o365_json_get_array_member (contact, "businessPhones");
+}
+
+void
+e_o365_contact_begin_business_phones (JsonBuilder *builder)
+{
+       e_o365_json_begin_array_member (builder, "businessPhones");
+}
+
+void
+e_o365_contact_end_business_phones (JsonBuilder *builder)
+{
+       e_o365_json_end_array_member (builder);
+}
+
+void
+e_o365_contact_add_business_phone (JsonBuilder *builder,
+                                  const gchar *value)
+{
+       g_return_if_fail (value && *value);
+
+       json_builder_add_string_value (builder, value);
+}
+
+JsonArray * /* const gchar * */
+e_o365_contact_get_categories (EO365Contact *contact)
+{
+       return e_o365_json_get_array_member (contact, "categories");
+}
+
+void
+e_o365_contact_begin_categories (JsonBuilder *builder)
+{
+       e_o365_json_begin_array_member (builder, "categories");
+}
+
+void
+e_o365_contact_end_categories (JsonBuilder *builder)
+{
+       e_o365_json_end_array_member (builder);
+}
+
+void
+e_o365_contact_add_category (JsonBuilder *builder,
+                            const gchar *category)
+{
+       g_return_if_fail (category && *category);
+
+       json_builder_add_string_value (builder, category);
+}
+
+JsonArray * /* const gchar * */
+e_o365_contact_get_children (EO365Contact *contact)
+{
+       return e_o365_json_get_array_member (contact, "children");
+}
+
+void
+e_o365_contact_begin_children (JsonBuilder *builder)
+{
+       e_o365_json_begin_array_member (builder, "children");
+}
+
+void
+e_o365_contact_end_children (JsonBuilder *builder)
+{
+       e_o365_json_end_array_member (builder);
+}
+
+void
+e_o365_contact_add_child (JsonBuilder *builder,
+                         const gchar *value)
+{
+       g_return_if_fail (value && *value);
+
+       json_builder_add_string_value (builder, value);
+}
+
+const gchar *
+e_o365_contact_get_company_name (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "companyName", NULL);
+}
+
+void
+e_o365_contact_add_company_name (JsonBuilder *builder,
+                                const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "companyName", value);
+}
+
+const gchar *
+e_o365_contact_get_department (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "department", NULL);
+}
+
+void
+e_o365_contact_add_department (JsonBuilder *builder,
+                              const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "department", value);
+}
+
+const gchar *
+e_o365_contact_get_display_name (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "displayName", NULL);
+}
+
+void
+e_o365_contact_add_display_name (JsonBuilder *builder,
+                                const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "displayName", value);
+}
+
+JsonArray * /* EO365EmailAddress * */
+e_o365_contact_get_email_addresses (EO365Contact *contact)
+{
+       return e_o365_json_get_array_member (contact, "emailAddresses");
+}
+
+void
+e_o365_contact_begin_email_addresses (JsonBuilder *builder)
+{
+       e_o365_json_begin_array_member (builder, "emailAddresses");
+}
+
+void
+e_o365_contact_end_email_addresses (JsonBuilder *builder)
+{
+       e_o365_json_end_array_member (builder);
+}
+
+const gchar *
+e_o365_contact_get_file_as (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "fileAs", NULL);
+}
+
+void
+e_o365_contact_add_file_as (JsonBuilder *builder,
+                           const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "fileAs", value);
+}
+
+const gchar *
+e_o365_contact_get_generation (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "generation", NULL);
+}
+
+void
+e_o365_contact_add_generation (JsonBuilder *builder,
+                              const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "generation", value);
+}
+
+const gchar *
+e_o365_contact_get_given_name (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "givenName", NULL);
+}
+
+void
+e_o365_contact_add_given_name (JsonBuilder *builder,
+                              const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "givenName", value);
+}
+
+EO365PhysicalAddress *
+e_o365_contact_get_home_address (EO365Contact *contact)
+{
+       return e_o365_json_get_object_member (contact, "homeAddress");
+}
+
+void
+e_o365_contact_add_home_address (JsonBuilder *builder,
+                                const gchar *city,
+                                const gchar *country_or_region,
+                                const gchar *postal_code,
+                                const gchar *state,
+                                const gchar *street)
+{
+       e_o365_add_physical_address (builder, "homeAddress", city, country_or_region, postal_code, state, 
street);
+}
+
+JsonArray * /* const gchar * */
+e_o365_contact_get_home_phones (EO365Contact *contact)
+{
+       return e_o365_json_get_array_member (contact, "homePhones");
+}
+
+void
+e_o365_contact_begin_home_phones (JsonBuilder *builder)
+{
+       e_o365_json_begin_array_member (builder, "homePhones");
+}
+
+void
+e_o365_contact_end_home_phones (JsonBuilder *builder)
+{
+       e_o365_json_end_array_member (builder);
+}
+
+void
+e_o365_contact_add_home_phone (JsonBuilder *builder,
+                              const gchar *value)
+{
+       g_return_if_fail (value && *value);
+
+       json_builder_add_string_value (builder, value);
+}
+
+JsonArray * /* const gchar * */
+e_o365_contact_get_im_addresses (EO365Contact *contact)
+{
+       return e_o365_json_get_array_member (contact, "imAddresses");
+}
+
+void
+e_o365_contact_begin_im_addresses (JsonBuilder *builder)
+{
+       e_o365_json_begin_array_member (builder, "imAddresses");
+}
+
+void
+e_o365_contact_end_im_addresses (JsonBuilder *builder)
+{
+       e_o365_json_end_array_member (builder);
+}
+
+void
+e_o365_contact_add_im_address (JsonBuilder *builder,
+                              const gchar *value)
+{
+       g_return_if_fail (value && *value);
+
+       json_builder_add_string_value (builder, value);
+}
+
+const gchar *
+e_o365_contact_get_initials (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "initials", NULL);
+}
+
+void
+e_o365_contact_add_initials (JsonBuilder *builder,
+                            const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "initials", value);
+}
+
+const gchar *
+e_o365_contact_get_job_title (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "jobTitle", NULL);
+}
+
+void
+e_o365_contact_add_job_title (JsonBuilder *builder,
+                             const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "jobTitle", value);
+}
+
+const gchar *
+e_o365_contact_get_manager (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "manager", NULL);
+}
+
+void
+e_o365_contact_add_manager (JsonBuilder *builder,
+                           const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "manager", value);
+}
+
+const gchar *
+e_o365_contact_get_middle_name (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "middleName", NULL);
+}
+
+void
+e_o365_contact_add_middle_name (JsonBuilder *builder,
+                               const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "middleName", value);
+}
+
+const gchar *
+e_o365_contact_get_mobile_phone (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "mobilePhone", NULL);
+}
+
+void
+e_o365_contact_add_mobile_phone (JsonBuilder *builder,
+                                const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "mobilePhone", value);
+}
+
+const gchar *
+e_o365_contact_get_nick_name (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "nickName", NULL);
+}
+
+void
+e_o365_contact_add_nick_name (JsonBuilder *builder,
+                             const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "nickName", value);
+}
+
+const gchar *
+e_o365_contact_get_office_location (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "officeLocation", NULL);
+}
+
+void
+e_o365_contact_add_office_location (JsonBuilder *builder,
+                                   const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "officeLocation", value);
+}
+
+EO365PhysicalAddress *
+e_o365_contact_get_other_address (EO365Contact *contact)
+{
+       return e_o365_json_get_object_member (contact, "otherAddress");
+}
+
+void
+e_o365_contact_add_other_address (JsonBuilder *builder,
+                                 const gchar *city,
+                                 const gchar *country_or_region,
+                                 const gchar *postal_code,
+                                 const gchar *state,
+                                 const gchar *street)
+{
+       e_o365_add_physical_address (builder, "otherAddress", city, country_or_region, postal_code, state, 
street);
+}
+
+const gchar *
+e_o365_contact_get_personal_notes (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "personalNotes", NULL);
+}
+
+void
+e_o365_contact_add_personal_notes (JsonBuilder *builder,
+                                  const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "personalNotes", value);
+}
+
+const gchar *
+e_o365_contact_get_profession (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "profession", NULL);
+}
+
+void
+e_o365_contact_add_profession (JsonBuilder *builder,
+                              const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "profession", value);
+}
+
+const gchar *
+e_o365_contact_get_spouse_name (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "spouseName", NULL);
+}
+
+void
+e_o365_contact_add_spouse_name (JsonBuilder *builder,
+                               const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "spouseName", value);
+}
+
+const gchar *
+e_o365_contact_get_surname (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "surname", NULL);
+}
+
+void
+e_o365_contact_add_surname (JsonBuilder *builder,
+                           const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "surname", value);
+}
+
+const gchar *
+e_o365_contact_get_title (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "title", NULL);
+}
+
+void
+e_o365_contact_add_title (JsonBuilder *builder,
+                         const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "title", value);
+}
+
+const gchar *
+e_o365_contact_get_yomi_company_name (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "yomiCompanyName", NULL);
+}
+
+void
+e_o365_contact_add_yomi_company_name (JsonBuilder *builder,
+                                     const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "yomiCompanyName", value);
+}
+
+const gchar *
+e_o365_contact_get_yomi_given_name (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "yomiGivenName", NULL);
+}
+
+void
+e_o365_contact_add_yomi_given_name (JsonBuilder *builder,
+                                   const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "yomiGivenName", value);
+}
+
+const gchar *
+e_o365_contact_get_yomi_surname (EO365Contact *contact)
+{
+       return e_o365_json_get_string_member (contact, "yomiSurname", NULL);
+}
+
+void
+e_o365_contact_add_yomi_surname (JsonBuilder *builder,
+                                const gchar *value)
+{
+       e_o365_json_add_nonempty_or_null_string_member (builder, "yomiSurname", value);
+}
diff --git a/src/Office365/common/e-o365-json-utils.h b/src/Office365/common/e-o365-json-utils.h
index 0f766d6f..2cd13d1c 100644
--- a/src/Office365/common/e-o365-json-utils.h
+++ b/src/Office365/common/e-o365-json-utils.h
@@ -23,6 +23,21 @@
 
 G_BEGIN_DECLS
 
+/* Just for better readability */
+#define EO365Attachment                        JsonObject
+#define EO365Category                  JsonObject
+#define EO365Contact                   JsonObject
+#define EO365DateTimeWithZone          JsonObject
+#define EO365EmailAddress              JsonObject
+#define EO365Folder                    JsonObject
+#define EO365FollowupFlag              JsonObject
+#define EO365InternetMessageHeader     JsonObject
+#define EO365ItemBody                  JsonObject
+#define EO365MailFolder                        JsonObject
+#define EO365MailMessage               JsonObject
+#define EO365PhysicalAddress           JsonObject
+#define EO365Recipient                 JsonObject
+
 typedef enum _EO365AttachmentDataType {
        E_O365_ATTACHMENT_DATA_TYPE_NOT_SET,
        E_O365_ATTACHMENT_DATA_TYPE_UNKNOWN,
@@ -61,18 +76,6 @@ typedef enum _EO365ItemBodyContentTypeType {
        E_O365_ITEM_BODY_CONTENT_TYPE_HTML
 } EO365ItemBodyContentTypeType;
 
-/* Just for better readability */
-#define EO365Attachment                        JsonObject
-#define EO365Category                  JsonObject
-#define EO365DateTimeWithZone          JsonObject
-#define EO365Folder                    JsonObject
-#define EO365FollowupFlag              JsonObject
-#define EO365InternetMessageHeader     JsonObject
-#define EO365ItemBody                  JsonObject
-#define EO365MailFolder                        JsonObject
-#define EO365MailMessage               JsonObject
-#define EO365Recipient                 JsonObject
-
 JsonArray *    e_o365_json_get_array_member            (JsonObject *object,
                                                         const gchar *member_name);
 void           e_o365_json_begin_array_member          (JsonBuilder *builder,
@@ -112,6 +115,13 @@ const gchar *      e_o365_json_get_string_member           (JsonObject *object,
 void           e_o365_json_add_string_member           (JsonBuilder *builder,
                                                         const gchar *member_name,
                                                         const gchar *value);
+void           e_o365_json_add_nonempty_string_member  (JsonBuilder *builder,
+                                                        const gchar *member_name,
+                                                        const gchar *value);
+void           e_o365_json_add_nonempty_or_null_string_member
+                                                       (JsonBuilder *builder,
+                                                        const gchar *member_name,
+                                                        const gchar *value);
 
 time_t         e_o365_get_date_time_offset_member      (JsonObject *object,
                                                         const gchar *member_name);
@@ -306,6 +316,156 @@ const gchar *     e_o365_file_attachment_get_content_id   (EO365Attachment *attachment
 void           e_o365_file_attachment_add_content_id   (JsonBuilder *builder,
                                                         const gchar *value);
 
+const gchar *  e_o365_email_address_get_name           (EO365EmailAddress *email);
+const gchar *  e_o365_email_address_get_address        (EO365EmailAddress *email);
+void           e_o365_add_email_address                (JsonBuilder *builder,
+                                                        const gchar *name,
+                                                        const gchar *address);
+const gchar *  e_o365_physical_address_get_city        (EO365PhysicalAddress *address);
+const gchar *  e_o365_physical_address_get_country_or_region
+                                                       (EO365PhysicalAddress *address);
+const gchar *  e_o365_physical_address_get_postal_code (EO365PhysicalAddress *address);
+const gchar *  e_o365_physical_address_get_state       (EO365PhysicalAddress *address);
+const gchar *  e_o365_physical_address_get_street      (EO365PhysicalAddress *address);
+void           e_o365_add_physical_address             (JsonBuilder *builder,
+                                                        const gchar *member_name,
+                                                        const gchar *city,
+                                                        const gchar *country_or_region,
+                                                        const gchar *postal_code,
+                                                        const gchar *state,
+                                                        const gchar *street);
+
+const gchar *  e_o365_contact_get_id                   (EO365Contact *contact);
+const gchar *  e_o365_contact_get_parent_folder_id     (EO365Contact *contact);
+const gchar *  e_o365_contact_get_change_key           (EO365Contact *contact);
+time_t         e_o365_contact_get_created_date_time    (EO365Contact *contact);
+time_t         e_o365_contact_get_last_modified_date_time
+                                                       (EO365Contact *contact);
+const gchar *  e_o365_contact_get_assistant_name       (EO365Contact *contact);
+void           e_o365_contact_add_assistant_name       (JsonBuilder *builder,
+                                                        const gchar *value);
+time_t         e_o365_contact_get_birthday             (EO365Contact *contact);
+void           e_o365_contact_add_birthday             (JsonBuilder *builder,
+                                                        time_t value);
+EO365PhysicalAddress *
+               e_o365_contact_get_business_address     (EO365Contact *contact);
+void           e_o365_contact_add_business_address     (JsonBuilder *builder,
+                                                        const gchar *city,
+                                                        const gchar *country_or_region,
+                                                        const gchar *postal_code,
+                                                        const gchar *state,
+                                                        const gchar *street);
+const gchar *  e_o365_contact_get_business_home_page   (EO365Contact *contact);
+void           e_o365_contact_add_business_home_page   (JsonBuilder *builder,
+                                                        const gchar *value);
+JsonArray *    e_o365_contact_get_business_phones      (EO365Contact *contact); /* const gchar * */
+void           e_o365_contact_begin_business_phones    (JsonBuilder *builder);
+void           e_o365_contact_end_business_phones      (JsonBuilder *builder);
+void           e_o365_contact_add_business_phone       (JsonBuilder *builder,
+                                                        const gchar *value);
+JsonArray *    e_o365_contact_get_categories           (EO365Contact *contact); /* const gchar * */
+void           e_o365_contact_begin_categories         (JsonBuilder *builder);
+void           e_o365_contact_end_categories           (JsonBuilder *builder);
+void           e_o365_contact_add_category             (JsonBuilder *builder,
+                                                        const gchar *category);
+JsonArray *    e_o365_contact_get_children             (EO365Contact *contact); /* const gchar * */
+void           e_o365_contact_begin_children           (JsonBuilder *builder);
+void           e_o365_contact_end_children             (JsonBuilder *builder);
+void           e_o365_contact_add_child                (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_company_name         (EO365Contact *contact);
+void           e_o365_contact_add_company_name         (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_department           (EO365Contact *contact);
+void           e_o365_contact_add_department           (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_display_name         (EO365Contact *contact);
+void           e_o365_contact_add_display_name         (JsonBuilder *builder,
+                                                        const gchar *value);
+JsonArray *    e_o365_contact_get_email_addresses      (EO365Contact *contact); /* EO365EmailAddress * */
+void           e_o365_contact_begin_email_addresses    (JsonBuilder *builder);
+void           e_o365_contact_end_email_addresses      (JsonBuilder *builder);
+const gchar *  e_o365_contact_get_file_as              (EO365Contact *contact);
+void           e_o365_contact_add_file_as              (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_generation           (EO365Contact *contact);
+void           e_o365_contact_add_generation           (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_given_name           (EO365Contact *contact);
+void           e_o365_contact_add_given_name           (JsonBuilder *builder,
+                                                        const gchar *value);
+EO365PhysicalAddress *
+               e_o365_contact_get_home_address         (EO365Contact *contact);
+void           e_o365_contact_add_home_address         (JsonBuilder *builder,
+                                                        const gchar *city,
+                                                        const gchar *country_or_region,
+                                                        const gchar *postal_code,
+                                                        const gchar *state,
+                                                        const gchar *street);
+JsonArray *    e_o365_contact_get_home_phones          (EO365Contact *contact); /* const gchar * */
+void           e_o365_contact_begin_home_phones        (JsonBuilder *builder);
+void           e_o365_contact_end_home_phones          (JsonBuilder *builder);
+void           e_o365_contact_add_home_phone           (JsonBuilder *builder,
+                                                        const gchar *value);
+JsonArray *    e_o365_contact_get_im_addresses         (EO365Contact *contact); /* const gchar * */
+void           e_o365_contact_begin_im_addresses       (JsonBuilder *builder);
+void           e_o365_contact_end_im_addresses         (JsonBuilder *builder);
+void           e_o365_contact_add_im_address           (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_initials             (EO365Contact *contact);
+void           e_o365_contact_add_initials             (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_job_title            (EO365Contact *contact);
+void           e_o365_contact_add_job_title            (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_manager              (EO365Contact *contact);
+void           e_o365_contact_add_manager              (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_middle_name          (EO365Contact *contact);
+void           e_o365_contact_add_middle_name          (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_mobile_phone         (EO365Contact *contact);
+void           e_o365_contact_add_mobile_phone         (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_nick_name            (EO365Contact *contact);
+void           e_o365_contact_add_nick_name            (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_office_location      (EO365Contact *contact);
+void           e_o365_contact_add_office_location      (JsonBuilder *builder,
+                                                        const gchar *value);
+EO365PhysicalAddress *
+               e_o365_contact_get_other_address        (EO365Contact *contact);
+void           e_o365_contact_add_other_address        (JsonBuilder *builder,
+                                                        const gchar *city,
+                                                        const gchar *country_or_region,
+                                                        const gchar *postal_code,
+                                                        const gchar *state,
+                                                        const gchar *street);
+const gchar *  e_o365_contact_get_personal_notes       (EO365Contact *contact);
+void           e_o365_contact_add_personal_notes       (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_profession           (EO365Contact *contact);
+void           e_o365_contact_add_profession           (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_spouse_name          (EO365Contact *contact);
+void           e_o365_contact_add_spouse_name          (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_surname              (EO365Contact *contact);
+void           e_o365_contact_add_surname              (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_title                (EO365Contact *contact);
+void           e_o365_contact_add_title                (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_yomi_company_name    (EO365Contact *contact);
+void           e_o365_contact_add_yomi_company_name    (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_yomi_given_name      (EO365Contact *contact);
+void           e_o365_contact_add_yomi_given_name      (JsonBuilder *builder,
+                                                        const gchar *value);
+const gchar *  e_o365_contact_get_yomi_surname         (EO365Contact *contact);
+void           e_o365_contact_add_yomi_surname         (JsonBuilder *builder,
+                                                        const gchar *value);
+
 G_END_DECLS
 
 #endif /* E_O365_JSON_UTILS_H */
diff --git a/src/Office365/registry/e-o365-backend.c b/src/Office365/registry/e-o365-backend.c
index d1b1c3c7..80fe6482 100644
--- a/src/Office365/registry/e-o365-backend.c
+++ b/src/Office365/registry/e-o365-backend.c
@@ -306,9 +306,7 @@ o365_backend_sync_folders_thread (GTask *task,
        success = e_o365_connection_get_folders_delta_sync (cnc, NULL, E_O365_FOLDER_KIND_CONTACTS, NULL, 
old_delta_link, 0,
                o365_backend_got_contact_folders_delta_cb, o365_backend, &new_delta_link, cancellable, 
&error);
 
-       if (old_delta_link && *old_delta_link && (
-           g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
-           g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_BAD_REQUEST))) {
+       if (old_delta_link && *old_delta_link && e_o365_connection_util_delta_token_failed (error)) {
                g_clear_pointer (&old_delta_link, g_free);
                g_clear_error (&error);
 
@@ -642,7 +640,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, cancellable, error);
+       result = e_o365_connection_authenticate_sync (cnc, NULL, E_O365_FOLDER_KIND_UNKNOWN, 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]