[evolution-ews/wip/mcrha/office365] Camel: Implement store's get_folder_info() for user folders



commit 115051ef543b53a6786ec9bae15bf63fec9bd41c
Author: Milan Crha <mcrha redhat com>
Date:   Tue Jun 16 18:59:59 2020 +0200

    Camel: Implement store's get_folder_info() for user folders

 src/Office365/camel/CMakeLists.txt                 |   2 +
 src/Office365/camel/camel-o365-store-summary.c     | 598 ++++++++++++++++++---
 src/Office365/camel/camel-o365-store-summary.h     |  36 +-
 src/Office365/camel/camel-o365-store.c             | 461 +++++++++++++---
 src/Office365/camel/camel-o365-utils.c             |  93 ++++
 src/Office365/camel/camel-o365-utils.h             |  29 +
 src/Office365/common/e-o365-connection.c           | 215 +++++++-
 src/Office365/common/e-o365-connection.h           |  29 +-
 src/Office365/common/e-o365-enums.h                |   2 +-
 .../evolution/e-mail-config-o365-backend.c         |   2 +-
 10 files changed, 1311 insertions(+), 156 deletions(-)
---
diff --git a/src/Office365/camel/CMakeLists.txt b/src/Office365/camel/CMakeLists.txt
index 65b471e1..64167043 100644
--- a/src/Office365/camel/CMakeLists.txt
+++ b/src/Office365/camel/CMakeLists.txt
@@ -14,6 +14,8 @@ set(SOURCES
        camel-o365-store-summary.h
        camel-o365-transport.c
        camel-o365-transport.h
+       camel-o365-utils.c
+       camel-o365-utils.h
 )
 
 add_library(cameloffice365 MODULE
diff --git a/src/Office365/camel/camel-o365-store-summary.c b/src/Office365/camel/camel-o365-store-summary.c
index 068b4637..367af65b 100644
--- a/src/Office365/camel/camel-o365-store-summary.c
+++ b/src/Office365/camel/camel-o365-store-summary.c
@@ -24,25 +24,78 @@
 #define STORE_GROUP_NAME "##storepriv##"
 #define DATA_VERSION 1
 
-#define LOCK(summary) g_rec_mutex_lock (&(summary->priv->property_lock))
-#define UNLOCK(summary) g_rec_mutex_unlock (&(summary->priv->property_lock))
+#define LOCK(_summary) g_rec_mutex_lock (&(_summary->priv->property_lock))
+#define UNLOCK(_summary) g_rec_mutex_unlock (&(_summary->priv->property_lock))
 
 struct _CamelO365StoreSummaryPrivate {
        GRecMutex property_lock;
-       gchar *path;
+       gchar *filename;
        GKeyFile *key_file;
        GFileMonitor *monitor_delete;
        gboolean dirty;
 
        /* Note: We use the *same* strings in both of these hash tables, and
-        * only id_fname_hash has g_free() hooked up as the destructor func.
-        * So entries must always be removed from fname_id_hash *first*. */
-       GHashTable *id_fname_hash; /* id ~> folder name */
-       GHashTable *fname_id_hash; /* folder name ~> id */
+        * only id_full_name_hash has g_free() hooked up as the destructor func.
+        * So entries must always be removed from full_name_id_hash *first*. */
+       GHashTable *id_full_name_hash; /* id ~> folder full name */
+       GHashTable *full_name_id_hash; /* folder full name ~> id */
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (CamelO365StoreSummary, camel_o365_store_summary, G_TYPE_OBJECT)
 
+static gchar *
+o365_store_summary_encode_folder_name (const gchar *display_name)
+{
+       GString *encoded;
+       const gchar *pos;
+
+       if (!display_name || !*display_name)
+               return NULL;
+
+       encoded = g_string_sized_new (strlen (display_name) + 4);
+
+       for (pos = display_name; *pos; pos++) {
+               if (strchr ("%?/", *pos))
+                       g_string_append_printf (encoded, "%%%02x", *pos);
+               else
+                       g_string_append_c (encoded, *pos);
+       }
+
+       return g_string_free (encoded, FALSE);
+}
+
+static gchar *
+o365_store_summary_decode_folder_name (gchar *pathpart)
+{
+       gchar *pos, *write_pos;
+
+       if (!pathpart || !*pathpart)
+               return pathpart;
+
+       pos = pathpart;
+       write_pos = pathpart;
+
+       while (*pos) {
+               if (*pos == '%' &&
+                   g_ascii_isxdigit (pos[1]) &&
+                   g_ascii_isxdigit (pos[2])) {
+                       *write_pos = (g_ascii_xdigit_value (pos[1]) << 4) + g_ascii_xdigit_value (pos[2]);
+
+                       pos += 2;
+               } else if (write_pos != pos) {
+                       *write_pos = *pos;
+               }
+
+               pos++;
+               write_pos++;
+       }
+
+       if (write_pos != pos)
+               *write_pos = 0;
+
+       return pathpart;
+}
+
 static void
 camel_o365_store_summary_migrate_data_locked (CamelO365StoreSummary *store_summary,
                                              gint from_version)
@@ -97,10 +150,10 @@ o365_store_summary_finalize (GObject *object)
        CamelO365StoreSummary *store_summary = CAMEL_O365_STORE_SUMMARY (object);
 
        g_rec_mutex_clear (&store_summary->priv->property_lock);
-       g_hash_table_destroy (store_summary->priv->id_fname_hash);
-       g_hash_table_destroy (store_summary->priv->fname_id_hash);
+       g_hash_table_destroy (store_summary->priv->full_name_id_hash);
+       g_hash_table_destroy (store_summary->priv->id_full_name_hash);
        g_key_file_free (store_summary->priv->key_file);
-       g_free (store_summary->priv->path);
+       g_free (store_summary->priv->filename);
 
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (camel_o365_store_summary_parent_class)->finalize (object);
@@ -121,25 +174,25 @@ camel_o365_store_summary_init (CamelO365StoreSummary *store_summary)
 {
        store_summary->priv = camel_o365_store_summary_get_instance_private (store_summary);
        store_summary->priv->key_file = g_key_file_new ();
-       store_summary->priv->id_fname_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-       store_summary->priv->fname_id_hash = g_hash_table_new (g_str_hash, g_str_equal); /* shared data with 
'id_fname_hash' */
+       store_summary->priv->id_full_name_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, 
g_free);
+       store_summary->priv->full_name_id_hash = g_hash_table_new (g_str_hash, g_str_equal); /* shared data 
with 'id_full_name_hash' */
 
        g_rec_mutex_init (&store_summary->priv->property_lock);
 }
 
 CamelO365StoreSummary *
-camel_o365_store_summary_new (const gchar *path)
+camel_o365_store_summary_new (const gchar *filename)
 {
        CamelO365StoreSummary *store_summary;
        GError *error = NULL;
        GFile *file;
 
-       g_return_val_if_fail (path != NULL, NULL);
+       g_return_val_if_fail (filename != NULL, NULL);
 
-       file = g_file_new_for_path (path);
+       file = g_file_new_for_path (filename);
 
        store_summary = g_object_new (CAMEL_TYPE_O365_STORE_SUMMARY, NULL);
-       store_summary->priv->path = g_strdup (path);
+       store_summary->priv->filename = g_strdup (filename);
        store_summary->priv->monitor_delete = g_file_monitor_file (file, G_FILE_MONITOR_SEND_MOVED, NULL, 
&error);
 
        if (!error) {
@@ -167,9 +220,14 @@ camel_o365_store_summary_load (CamelO365StoreSummary *store_summary,
 
        LOCK (store_summary);
 
-       success = g_key_file_load_from_file (store_summary->priv->key_file, store_summary->priv->path, 
G_KEY_FILE_NONE, &local_error);
+       g_hash_table_remove_all (store_summary->priv->full_name_id_hash);
+       g_hash_table_remove_all (store_summary->priv->id_full_name_hash);
 
-       if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+       store_summary->priv->dirty = FALSE;
+
+       success = g_key_file_load_from_file (store_summary->priv->key_file, store_summary->priv->filename, 
G_KEY_FILE_NONE, &local_error);
+
+       if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
                g_key_file_set_integer (store_summary->priv->key_file, STORE_GROUP_NAME, "Version", 
DATA_VERSION);
 
                g_clear_error (&local_error);
@@ -182,7 +240,9 @@ camel_o365_store_summary_load (CamelO365StoreSummary *store_summary,
                version = g_key_file_get_integer (store_summary->priv->key_file, STORE_GROUP_NAME, "Version", 
NULL);
 
                if (version && version < DATA_VERSION)
-                   camel_o365_store_summary_migrate_data_locked (store_summary, version);
+                       camel_o365_store_summary_migrate_data_locked (store_summary, version);
+
+               camel_o365_store_summary_rebuild_hashes (store_summary);
        }
 
        UNLOCK (store_summary);
@@ -194,14 +254,14 @@ gboolean
 camel_o365_store_summary_save (CamelO365StoreSummary *store_summary,
                               GError **error)
 {
-       gboolean success;
+       gboolean success = TRUE;
 
        g_return_val_if_fail (CAMEL_IS_O365_STORE_SUMMARY (store_summary), FALSE);
 
        LOCK (store_summary);
 
        if (store_summary->priv->dirty) {
-               success = g_key_file_save_to_file (store_summary->priv->key_file, store_summary->priv->path, 
error);
+               success = g_key_file_save_to_file (store_summary->priv->key_file, 
store_summary->priv->filename, error);
 
                if (success)
                        store_summary->priv->dirty = FALSE;
@@ -219,13 +279,13 @@ camel_o365_store_summary_clear (CamelO365StoreSummary *store_summary)
 
        LOCK (store_summary);
 
-       store_summary->priv->dirty = g_hash_table_size (store_summary->priv->id_fname_hash) > 0;
+       store_summary->priv->dirty = g_hash_table_size (store_summary->priv->id_full_name_hash) > 0;
 
        g_key_file_free (store_summary->priv->key_file);
        store_summary->priv->key_file = g_key_file_new ();
 
-       g_hash_table_remove_all (store_summary->priv->fname_id_hash);
-       g_hash_table_remove_all (store_summary->priv->id_fname_hash);
+       g_hash_table_remove_all (store_summary->priv->full_name_id_hash);
+       g_hash_table_remove_all (store_summary->priv->id_full_name_hash);
 
        UNLOCK (store_summary);
 }
@@ -246,6 +306,109 @@ camel_o365_store_summary_unlock (CamelO365StoreSummary *store_summary)
        UNLOCK (store_summary);
 }
 
+static void
+o365_store_summary_build_full_name (const gchar *id,
+                                   GHashTable *id_folder_name,
+                                   GHashTable *id_parent_id,
+                                   GHashTable *covered,
+                                   GString *inout_full_name)
+{
+       const gchar *parent_id;
+
+       g_return_if_fail (id != NULL);
+
+       if (g_hash_table_contains (covered, id))
+               return;
+
+       g_hash_table_insert (covered, (gpointer) id, NULL);
+
+       parent_id = g_hash_table_lookup (id_parent_id, id);
+
+       if (parent_id && *parent_id && g_hash_table_contains (id_folder_name, parent_id))
+               o365_store_summary_build_full_name (parent_id, id_folder_name, id_parent_id, covered, 
inout_full_name);
+
+       if (inout_full_name->len)
+               g_string_append_c (inout_full_name, '/');
+
+       g_string_append (inout_full_name, g_hash_table_lookup (id_folder_name, id));
+}
+
+void
+camel_o365_store_summary_rebuild_hashes (CamelO365StoreSummary *store_summary)
+{
+       GHashTable *id_folder_name;
+       GHashTable *id_parent_id;
+       gchar **groups;
+       gint ii;
+
+       g_return_if_fail (CAMEL_IS_O365_STORE_SUMMARY (store_summary));
+
+       LOCK (store_summary);
+
+       g_hash_table_remove_all (store_summary->priv->full_name_id_hash);
+       g_hash_table_remove_all (store_summary->priv->id_full_name_hash);
+
+       id_folder_name = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+       id_parent_id = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+
+       groups = g_key_file_get_groups (store_summary->priv->key_file, NULL);
+
+       for (ii = 0; groups[ii]; ii++) {
+               const gchar *group = groups[ii];
+
+               if (g_ascii_strcasecmp (group, STORE_GROUP_NAME) != 0 &&
+                   g_key_file_has_key (store_summary->priv->key_file, group, "DisplayName", NULL)) {
+                       gchar *display_name, *folder_name;
+
+                       display_name = g_key_file_get_string (store_summary->priv->key_file, group, 
"DisplayName", NULL);
+                       folder_name = o365_store_summary_encode_folder_name (display_name);
+
+                       g_hash_table_insert (id_folder_name, (gpointer) group, folder_name);
+                       g_hash_table_insert (id_parent_id, (gpointer) group,
+                               camel_o365_store_summary_dup_folder_parent_id (store_summary, group));
+
+                       g_free (display_name);
+               }
+       }
+
+       if (g_hash_table_size (id_folder_name)) {
+               GHashTable *covered;
+               GHashTableIter iter;
+               gpointer key;
+
+               covered = g_hash_table_new (g_str_hash, g_str_equal);
+
+               g_hash_table_iter_init (&iter, id_folder_name);
+
+               while (g_hash_table_iter_next (&iter, &key, NULL)) {
+                       const gchar *id = key;
+                       GString *full_name_str;
+
+                       g_hash_table_remove_all (covered);
+
+                       full_name_str = g_string_sized_new (16);
+
+                       o365_store_summary_build_full_name (id, id_folder_name, id_parent_id, covered, 
full_name_str);
+
+                       if (full_name_str->len) {
+                               gchar *id_dup = g_strdup (id);
+                               gchar *full_name = g_string_free (full_name_str, FALSE);
+
+                               g_hash_table_insert (store_summary->priv->id_full_name_hash, id_dup, 
full_name);
+                               g_hash_table_insert (store_summary->priv->full_name_id_hash, full_name, 
id_dup);
+                       } else {
+                               g_string_free (full_name_str, TRUE);
+                       }
+               }
+
+               g_hash_table_destroy (covered);
+       }
+
+       g_strfreev (groups);
+
+       UNLOCK (store_summary);
+}
+
 void
 camel_o365_store_summary_set_delta_link (CamelO365StoreSummary *store_summary,
                                         const gchar *delta_link)
@@ -281,8 +444,53 @@ camel_o365_store_summary_dup_delta_link (CamelO365StoreSummary *store_summary)
        return value;
 }
 
+gboolean
+camel_o365_store_summary_has_folder (CamelO365StoreSummary *store_summary,
+                                    const gchar *id)
+{
+       gboolean has;
+
+       g_return_val_if_fail (CAMEL_IS_O365_STORE_SUMMARY (store_summary), FALSE);
+       g_return_val_if_fail (id != NULL, FALSE);
+
+       LOCK (store_summary);
+
+       has = g_hash_table_contains (store_summary->priv->id_full_name_hash, id);
+
+       UNLOCK (store_summary);
+
+       return has;
+}
+
+void
+camel_o365_store_summary_remove_folder (CamelO365StoreSummary *store_summary,
+                                       const gchar *id)
+{
+       const gchar *full_name;
+
+       g_return_if_fail (CAMEL_IS_O365_STORE_SUMMARY (store_summary));
+       g_return_if_fail (id != NULL);
+
+       LOCK (store_summary);
+
+       full_name = g_hash_table_lookup (store_summary->priv->id_full_name_hash, id);
+
+       if (full_name) {
+               g_hash_table_remove (store_summary->priv->full_name_id_hash, full_name);
+               g_hash_table_remove (store_summary->priv->id_full_name_hash, id);
+
+               store_summary->priv->dirty = store_summary->priv->dirty ||
+                       g_key_file_has_group (store_summary->priv->key_file, id);
+
+               g_key_file_remove_group (store_summary->priv->key_file, id, NULL);
+       }
+
+       UNLOCK (store_summary);
+}
+
 void
 camel_o365_store_summary_set_folder (CamelO365StoreSummary *store_summary,
+                                    gboolean with_hashes_update,
                                     const gchar *id,
                                     const gchar *parent_id,
                                     const gchar *display_name,
@@ -301,7 +509,6 @@ camel_o365_store_summary_set_folder (CamelO365StoreSummary *store_summary,
 
        LOCK (store_summary);
 
-       camel_o365_store_summary_set_folder_display_name (store_summary, id, display_name);
        camel_o365_store_summary_set_folder_parent_id (store_summary, id, parent_id);
        camel_o365_store_summary_set_folder_total_count (store_summary, id, total_count);
        camel_o365_store_summary_set_folder_unread_count (store_summary, id, unread_count);
@@ -322,6 +529,9 @@ camel_o365_store_summary_set_folder (CamelO365StoreSummary *store_summary,
                changed = TRUE;
        }
 
+       /* Set display name as the last, because it updates internal hashes and depends on the stored data */
+       camel_o365_store_summary_set_folder_display_name (store_summary, id, display_name, 
with_hashes_update);
+
        if (changed)
                store_summary->priv->dirty = TRUE;
 
@@ -331,8 +541,9 @@ camel_o365_store_summary_set_folder (CamelO365StoreSummary *store_summary,
 gboolean
 camel_o365_store_summary_get_folder (CamelO365StoreSummary *store_summary,
                                     const gchar *id,
-                                    gchar **out_parent_id,
+                                    gchar **out_full_name,
                                     gchar **out_display_name,
+                                    gchar **out_parent_id,
                                     gint32 *out_total_count,
                                     gint32 *out_unread_count,
                                     guint32 *out_flags,
@@ -350,6 +561,9 @@ camel_o365_store_summary_get_folder (CamelO365StoreSummary *store_summary,
        found = g_key_file_has_group (store_summary->priv->key_file, id);
 
        if (found) {
+               if (out_full_name)
+                       *out_full_name = g_strdup (g_hash_table_lookup 
(store_summary->priv->id_full_name_hash, id));
+
                if (out_display_name)
                        *out_display_name = g_key_file_get_string (store_summary->priv->key_file, id, 
"DisplayName", NULL);
 
@@ -380,79 +594,231 @@ camel_o365_store_summary_get_folder (CamelO365StoreSummary *store_summary,
        return found;
 }
 
-void
-camel_o365_store_summary_set_folder_parent_id (CamelO365StoreSummary *store_summary,
-                                              const gchar *id,
-                                              const gchar *parent_id)
+gchar *
+camel_o365_store_summary_dup_folder_full_name (CamelO365StoreSummary *store_summary,
+                                              const gchar *id)
 {
-       g_return_if_fail (CAMEL_IS_O365_STORE_SUMMARY (store_summary));
-       g_return_if_fail (id != NULL);
+       gchar *value = NULL;
 
-       LOCK (store_summary);
+       if (!camel_o365_store_summary_get_folder (store_summary, id, &value, NULL, NULL, NULL, NULL, NULL, 
NULL, NULL, NULL))
+               value = NULL;
 
-       if (parent_id && *parent_id) {
-               gchar *current_parent_id;
+       return value;
+}
 
-               current_parent_id = g_key_file_get_string (store_summary->priv->key_file, id, "ParentId", 
NULL);
+typedef struct _IdFullNameData {
+       gchar *id;
+       gchar *full_name;
+} IdFullNameData;
 
-               if (g_strcmp0 (current_parent_id, parent_id) != 0) {
-                       g_key_file_set_string (store_summary->priv->key_file, id, "ParentId", parent_id);
-                       store_summary->priv->dirty = TRUE;
-               }
+static IdFullNameData *
+id_full_name_data_new (gchar *id,
+                      gchar *full_name)
+{
+       IdFullNameData *ifnd;
 
-               g_free (current_parent_id);
-       } else if (g_key_file_has_key (store_summary->priv->key_file, id, "ParentId", NULL)) {
-               g_key_file_remove_key (store_summary->priv->key_file, id, "ParentId", NULL);
+       ifnd = g_slice_new (IdFullNameData);
+       ifnd->id = id;
+       ifnd->full_name = full_name;
+
+       return ifnd;
+}
+
+static void
+id_full_name_data_free (gpointer ptr)
+{
+       IdFullNameData *ifnd = ptr;
+
+       if (ifnd) {
+               g_free (ifnd->id);
+               g_free (ifnd->full_name);
+               g_slice_free (IdFullNameData, ifnd);
+       }
+}
+
+typedef struct _RemovePrefixedData {
+       GHashTable *full_name_id_hash;
+       const gchar *prefix;
+       gint prefix_len;
+       GSList *removed; /* IdFullNameData * */
+} RemovePrefixedData;
+
+static gboolean
+o365_remove_prefixed_cb (gpointer key,
+                        gpointer value,
+                        gpointer user_data)
+{
+       RemovePrefixedData *rpd = user_data;
+       gchar *id = key, *full_name = value;
+
+       g_return_val_if_fail (rpd != NULL, FALSE);
+       g_return_val_if_fail (full_name != NULL, FALSE);
+
+       if (g_str_has_prefix (full_name, rpd->prefix) &&
+           (!full_name[rpd->prefix_len] || full_name[rpd->prefix_len] == '/')) {
+               g_hash_table_remove (rpd->full_name_id_hash, full_name);
+
+               rpd->removed = g_slist_prepend (rpd->removed, id_full_name_data_new (id, full_name));
+
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static gchar *
+o365_store_summary_build_new_full_name (const gchar *old_full_name,
+                                       const gchar *new_display_name)
+{
+       gchar *encoded;
+       GString *full_name;
+       const gchar *last_slash;
+
+       g_return_val_if_fail (old_full_name != NULL, NULL);
+       g_return_val_if_fail (new_display_name != NULL, NULL);
+
+       last_slash = strrchr (old_full_name, '/');
+       encoded = o365_store_summary_encode_folder_name (new_display_name);
+       full_name = g_string_sized_new ((last_slash ? (last_slash - old_full_name) : 0) + strlen (encoded) + 
2);
+
+       if (last_slash)
+               g_string_append_len (full_name, old_full_name, last_slash - old_full_name + 1);
+
+       g_string_append (full_name, encoded);
+
+       g_free (encoded);
+
+       return g_string_free (full_name, FALSE);
+}
+
+gboolean
+camel_o365_store_summary_set_folder_display_name (CamelO365StoreSummary *store_summary,
+                                                 const gchar *id,
+                                                 const gchar *display_name,
+                                                 gboolean with_hashes_update)
+{
+       gchar *current_display_name;
+       gboolean changed = FALSE;
+
+       g_return_val_if_fail (CAMEL_IS_O365_STORE_SUMMARY (store_summary), FALSE);
+       g_return_val_if_fail (id != NULL, FALSE);
+       g_return_val_if_fail (display_name != NULL, FALSE);
+
+       LOCK (store_summary);
+
+       current_display_name = g_key_file_get_string (store_summary->priv->key_file, id, "DisplayName", NULL);
+
+       if (g_strcmp0 (current_display_name, display_name) != 0) {
+               const gchar *old_full_name;
+
+               g_key_file_set_string (store_summary->priv->key_file, id, "DisplayName", display_name);
                store_summary->priv->dirty = TRUE;
+
+               changed = TRUE;
+
+               if (with_hashes_update) {
+                       old_full_name = g_hash_table_lookup (store_summary->priv->id_full_name_hash, id);
+
+                       if (old_full_name) {
+                               RemovePrefixedData rpd;
+                               gchar *new_full_name;
+                               gint diff;
+                               GSList *link;
+
+                               rpd.full_name_id_hash = store_summary->priv->full_name_id_hash;
+                               rpd.prefix = old_full_name;
+                               rpd.prefix_len = strlen (old_full_name);
+                               rpd.removed = NULL;
+
+                               g_hash_table_foreach_remove (store_summary->priv->id_full_name_hash, 
o365_remove_prefixed_cb, &rpd);
+
+                               new_full_name = o365_store_summary_build_new_full_name (old_full_name, 
display_name);
+                               diff = strlen (new_full_name) - rpd.prefix_len;
+
+                               for (link = rpd.removed; link; link = g_slist_next (link)) {
+                                       IdFullNameData *ifnd = link->data;
+                                       GString *fixed_full_name_str;
+                                       gchar *fixed_full_name;
+                                       gint old_full_name_len;
+
+                                       old_full_name_len = strlen (ifnd->full_name);
+                                       fixed_full_name_str = g_string_sized_new (old_full_name_len + diff + 
2);
+
+                                       g_string_append (fixed_full_name_str, new_full_name);
+
+                                       if (old_full_name_len > rpd.prefix_len)
+                                               g_string_append (fixed_full_name_str, ifnd->full_name + 
rpd.prefix_len);
+
+                                       fixed_full_name = g_string_free (fixed_full_name_str, FALSE);
+
+                                       g_hash_table_insert (store_summary->priv->id_full_name_hash, 
ifnd->id, fixed_full_name);
+                                       g_hash_table_insert (store_summary->priv->full_name_id_hash, 
fixed_full_name, ifnd->id);
+
+                                       /* To not be freed by id_full_name_data_free() below */
+                                       ifnd->id = NULL;
+                               }
+
+                               g_slist_free_full (rpd.removed, id_full_name_data_free);
+                               g_free (new_full_name);
+                       }
+               }
        }
 
+       g_free (current_display_name);
+
        UNLOCK (store_summary);
+
+       return changed;
 }
 
 gchar *
-camel_o365_store_summary_dup_folder_parent_id (CamelO365StoreSummary *store_summary,
-                                              const gchar *id)
+camel_o365_store_summary_dup_folder_display_name (CamelO365StoreSummary *store_summary,
+                                                 const gchar *id)
 {
        gchar *value = NULL;
 
-       if (!camel_o365_store_summary_get_folder (store_summary, id, &value, NULL, NULL, NULL, NULL, NULL, 
NULL, NULL))
+       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, &value, NULL, NULL, NULL, NULL, 
NULL, NULL, NULL))
                value = NULL;
 
        return value;
 }
 
 void
-camel_o365_store_summary_set_folder_display_name (CamelO365StoreSummary *store_summary,
-                                                 const gchar *id,
-                                                 const gchar *display_name)
+camel_o365_store_summary_set_folder_parent_id (CamelO365StoreSummary *store_summary,
+                                              const gchar *id,
+                                              const gchar *parent_id)
 {
-       const gchar *current_display_name;
-
        g_return_if_fail (CAMEL_IS_O365_STORE_SUMMARY (store_summary));
        g_return_if_fail (id != NULL);
-       g_return_if_fail (display_name != NULL);
 
        LOCK (store_summary);
 
-       current_display_name = g_hash_table_lookup (store_summary->priv->id_fname_hash, id);
+       if (parent_id && *parent_id) {
+               gchar *current_parent_id;
 
-       if (g_strcmp0 (current_display_name, display_name) != 0) {
-               g_key_file_set_string (store_summary->priv->key_file, id, "DisplayName", display_name);
-               store_summary->priv->dirty = TRUE;
+               current_parent_id = g_key_file_get_string (store_summary->priv->key_file, id, "ParentId", 
NULL);
+
+               if (g_strcmp0 (current_parent_id, parent_id) != 0) {
+                       g_key_file_set_string (store_summary->priv->key_file, id, "ParentId", parent_id);
+                       store_summary->priv->dirty = TRUE;
+               }
 
-               // TODO: update hashes on display name change                   
+               g_free (current_parent_id);
+       } else if (g_key_file_has_key (store_summary->priv->key_file, id, "ParentId", NULL)) {
+               g_key_file_remove_key (store_summary->priv->key_file, id, "ParentId", NULL);
+               store_summary->priv->dirty = TRUE;
        }
 
        UNLOCK (store_summary);
 }
 
 gchar *
-camel_o365_store_summary_dup_folder_display_name (CamelO365StoreSummary *store_summary,
-                                                 const gchar *id)
+camel_o365_store_summary_dup_folder_parent_id (CamelO365StoreSummary *store_summary,
+                                              const gchar *id)
 {
        gchar *value = NULL;
 
-       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, &value, NULL, NULL, NULL, NULL, 
NULL, NULL))
+       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, &value, NULL, NULL, NULL, 
NULL, NULL, NULL))
                value = NULL;
 
        return value;
@@ -482,7 +848,7 @@ camel_o365_store_summary_get_folder_total_count (CamelO365StoreSummary *store_su
 {
        gint32 value = 0;
 
-       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, &value, NULL, NULL, NULL, 
NULL, NULL))
+       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, &value, NULL, NULL, 
NULL, NULL, NULL))
                value = 0;
 
        return value;
@@ -512,7 +878,7 @@ camel_o365_store_summary_get_folder_unread_count (CamelO365StoreSummary *store_s
 {
        gint32 value = 0;
 
-       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, &value, NULL, NULL, 
NULL, NULL))
+       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, &value, NULL, 
NULL, NULL, NULL))
                value = 0;
 
        return value;
@@ -542,7 +908,7 @@ camel_o365_store_summary_get_folder_flags (CamelO365StoreSummary *store_summary,
 {
        guint32 value = 0;
 
-       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, &value, NULL, 
NULL, NULL))
+       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, NULL, &value, 
NULL, NULL, NULL))
                value = 0;
 
        return value;
@@ -554,7 +920,7 @@ camel_o365_store_summary_get_folder_kind (CamelO365StoreSummary *store_summary,
 {
        EO365FolderKind value = E_O365_FOLDER_KIND_UNKNOWN;
 
-       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, NULL, &value, 
NULL, NULL))
+       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, NULL, NULL, 
&value, NULL, NULL))
                value = E_O365_FOLDER_KIND_UNKNOWN;
 
        return value;
@@ -566,7 +932,7 @@ camel_o365_store_summary_get_folder_is_foreign (CamelO365StoreSummary *store_sum
 {
        gboolean value = FALSE;
 
-       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, NULL, NULL, 
&value, NULL))
+       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, NULL, NULL, 
NULL, &value, NULL))
                value = FALSE;
 
        return value;
@@ -578,8 +944,108 @@ camel_o365_store_summary_get_folder_is_public (CamelO365StoreSummary *store_summ
 {
        gboolean value = FALSE;
 
-       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, NULL, NULL, 
NULL, &value))
+       if (!camel_o365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, NULL, NULL, 
NULL, NULL, &value))
                value = FALSE;
 
        return value;
 }
+
+CamelFolderInfo *
+camel_o365_store_summary_build_folder_info_for_id (CamelO365StoreSummary *store_summary,
+                                                  const gchar *id)
+{
+       CamelFolderInfo *info;
+       gchar *full_name = NULL;
+       gchar *display_name = NULL;
+       gint32 total_count = 0;
+       gint32 unread_count = 0;
+       guint32 flags = 0;
+
+       g_return_val_if_fail (CAMEL_IS_O365_STORE_SUMMARY (store_summary), NULL);
+       g_return_val_if_fail (id != NULL, NULL);
+
+       LOCK (store_summary);
+
+       if (camel_o365_store_summary_get_folder (store_summary, id, &full_name, &display_name, NULL, 
&total_count, &unread_count, &flags, NULL, NULL, NULL)) {
+               info = camel_folder_info_new ();
+               info->full_name = full_name;
+               info->display_name = display_name;
+               info->flags = flags;
+               info->unread = unread_count;
+               info->total = total_count;
+       } else {
+               info = NULL;
+       }
+
+       UNLOCK (store_summary);
+
+       return info;
+}
+
+typedef struct _GatherInfosData {
+       CamelO365StoreSummary *store_summary;
+       GPtrArray *folder_infos;
+       const gchar *prefix;
+       gint prefix_len;
+       gboolean recursive;
+} GatherInfosData;
+
+static void
+o365_store_summary_gather_folder_infos (gpointer key,
+                                       gpointer value,
+                                       gpointer user_data)
+{
+       const gchar *id = key, *full_name = value;
+       GatherInfosData *gid = user_data;
+
+       g_return_if_fail (full_name != NULL);
+       g_return_if_fail (gid != NULL);
+
+       if (!gid->prefix_len || (g_str_has_prefix (full_name, gid->prefix) &&
+           full_name[gid->prefix_len] == '/')) {
+               const gchar *without_prefix = full_name + gid->prefix_len + (gid->prefix_len > 0 ? 1 : 0);
+
+               if (gid->recursive || !strchr (without_prefix, '/')) {
+                       CamelFolderInfo *info;
+
+                       info = camel_o365_store_summary_build_folder_info_for_id (gid->store_summary, id);
+
+                       if (info)
+                               g_ptr_array_add (gid->folder_infos, info);
+                       else
+                               g_warning ("%s: Failed to build folder info for id:'%s' full_name:'%s'", 
G_STRFUNC, id, full_name);
+               }
+       }
+}
+
+CamelFolderInfo *
+camel_o365_store_summary_build_folder_info (CamelO365StoreSummary *store_summary,
+                                           const gchar *top,
+                                           gboolean recursive)
+{
+       CamelFolderInfo *info = NULL;
+       GatherInfosData gid;
+
+       g_return_val_if_fail (CAMEL_IS_O365_STORE_SUMMARY (store_summary), NULL);
+
+       if (!top)
+               top = "";
+
+       LOCK (store_summary);
+
+       gid.store_summary = store_summary;
+       gid.folder_infos = g_ptr_array_new ();
+       gid.prefix = top;
+       gid.prefix_len = strlen (top);
+       gid.recursive = recursive;
+
+       g_hash_table_foreach (store_summary->priv->id_full_name_hash, o365_store_summary_gather_folder_infos, 
&gid);
+
+       info = camel_folder_info_build (gid.folder_infos, NULL, '/', TRUE);
+
+       UNLOCK (store_summary);
+
+       g_ptr_array_free (gid.folder_infos, TRUE);
+
+       return info;
+}
diff --git a/src/Office365/camel/camel-o365-store-summary.h b/src/Office365/camel/camel-o365-store-summary.h
index 6a24463d..48cd4570 100644
--- a/src/Office365/camel/camel-o365-store-summary.h
+++ b/src/Office365/camel/camel-o365-store-summary.h
@@ -59,7 +59,7 @@ struct _CamelO365StoreSummaryClass {
 GType          camel_o365_store_summary_get_type       (void);
 
 CamelO365StoreSummary *
-               camel_o365_store_summary_new            (const gchar *path);
+               camel_o365_store_summary_new            (const gchar *filename);
 gboolean       camel_o365_store_summary_load           (CamelO365StoreSummary *store_summary,
                                                         GError **error);
 gboolean       camel_o365_store_summary_save           (CamelO365StoreSummary *store_summary,
@@ -67,10 +67,16 @@ gboolean    camel_o365_store_summary_save           (CamelO365StoreSummary *store_summary,
 void           camel_o365_store_summary_clear          (CamelO365StoreSummary *store_summary);
 void           camel_o365_store_summary_lock           (CamelO365StoreSummary *store_summary);
 void           camel_o365_store_summary_unlock         (CamelO365StoreSummary *store_summary);
+void           camel_o365_store_summary_rebuild_hashes (CamelO365StoreSummary *store_summary);
 void           camel_o365_store_summary_set_delta_link (CamelO365StoreSummary *store_summary,
                                                         const gchar *delta_link);
 gchar *                camel_o365_store_summary_dup_delta_link (CamelO365StoreSummary *store_summary);
+gboolean       camel_o365_store_summary_has_folder     (CamelO365StoreSummary *store_summary,
+                                                        const gchar *id);
+void           camel_o365_store_summary_remove_folder  (CamelO365StoreSummary *store_summary,
+                                                        const gchar *id);
 void           camel_o365_store_summary_set_folder     (CamelO365StoreSummary *store_summary,
+                                                        gboolean with_hashes_update,
                                                         const gchar *id,
                                                         const gchar *parent_id,
                                                         const gchar *display_name,
@@ -82,26 +88,31 @@ void                camel_o365_store_summary_set_folder     (CamelO365StoreSummary 
*store_summary,
                                                         gboolean is_public);
 gboolean       camel_o365_store_summary_get_folder     (CamelO365StoreSummary *store_summary,
                                                         const gchar *id,
-                                                        gchar **out_parent_id,
+                                                        gchar **out_full_name,
                                                         gchar **out_display_name,
+                                                        gchar **out_parent_id,
                                                         gint32 *out_total_count,
                                                         gint32 *out_unread_count,
                                                         guint32 *out_flags,
                                                         EO365FolderKind *out_kind,
                                                         gboolean *out_is_foreign,
                                                         gboolean *out_is_public);
-void           camel_o365_store_summary_set_folder_parent_id
+gchar *                camel_o365_store_summary_dup_folder_full_name
+                                                       (CamelO365StoreSummary *store_summary,
+                                                        const gchar *id);
+gboolean       camel_o365_store_summary_set_folder_display_name
                                                        (CamelO365StoreSummary *store_summary,
                                                         const gchar *id,
-                                                        const gchar *parent_id);
-gchar *                camel_o365_store_summary_dup_folder_parent_id
+                                                        const gchar *display_name,
+                                                        gboolean with_hashes_update);
+gchar *                camel_o365_store_summary_dup_folder_display_name
                                                        (CamelO365StoreSummary *store_summary,
                                                         const gchar *id);
-void           camel_o365_store_summary_set_folder_display_name
+void           camel_o365_store_summary_set_folder_parent_id
                                                        (CamelO365StoreSummary *store_summary,
                                                         const gchar *id,
-                                                        const gchar *display_name);
-gchar *                camel_o365_store_summary_dup_folder_display_name
+                                                        const gchar *parent_id);
+gchar *                camel_o365_store_summary_dup_folder_parent_id
                                                        (CamelO365StoreSummary *store_summary,
                                                         const gchar *id);
 void           camel_o365_store_summary_set_folder_total_count
@@ -133,6 +144,15 @@ gboolean   camel_o365_store_summary_get_folder_is_foreign
 gboolean       camel_o365_store_summary_get_folder_is_public
                                                        (CamelO365StoreSummary *store_summary,
                                                         const gchar *id);
+CamelFolderInfo *
+               camel_o365_store_summary_build_folder_info_for_id
+                                                       (CamelO365StoreSummary *store_summary,
+                                                        const gchar *id);
+CamelFolderInfo *
+               camel_o365_store_summary_build_folder_info
+                                                       (CamelO365StoreSummary *store_summary,
+                                                        const gchar *top,
+                                                        gboolean recursive);
 G_END_DECLS
 
 #endif /* CAMEL_O365_STORE_SUMMARY_H */
diff --git a/src/Office365/camel/camel-o365-store.c b/src/Office365/camel/camel-o365-store.c
index 7efb4f1d..bb17054b 100644
--- a/src/Office365/camel/camel-o365-store.c
+++ b/src/Office365/camel/camel-o365-store.c
@@ -21,12 +21,21 @@
 #include <glib/gstdio.h>
 
 #include "common/camel-o365-settings.h"
+#include "common/e-o365-connection.h"
+#include "common/e-o365-json-utils.h"
+#include "camel-o365-store-summary.h"
+#include "camel-o365-utils.h"
 
 #include "camel-o365-store.h"
 
+#define LOCK(_store) g_rec_mutex_lock (&(_store->priv->property_lock))
+#define UNLOCK(_store) g_rec_mutex_unlock (&(_store->priv->property_lock))
+
 struct _CamelO365StorePrivate {
        GRecMutex property_lock;
        gchar *storage_path;
+       CamelO365StoreSummary *summary;
+       EO365Connection *cnc;
 };
 
 static void camel_o365_store_initable_init (GInitableIface *iface);
@@ -45,47 +54,6 @@ G_DEFINE_TYPE_WITH_CODE (CamelO365Store, camel_o365_store, CAMEL_TYPE_OFFLINE_ST
        G_IMPLEMENT_INTERFACE (CAMEL_TYPE_SUBSCRIBABLE, camel_o365_subscribable_init)
        G_ADD_PRIVATE (CamelO365Store))
 
-static void
-o365_store_set_property (GObject *object,
-                        guint property_id,
-                        const GValue *value,
-                        GParamSpec *pspec)
-{
-       switch (property_id) {
-               case PROP_CONNECTABLE:
-                       camel_network_service_set_connectable (
-                               CAMEL_NETWORK_SERVICE (object),
-                               g_value_get_object (value));
-                       return;
-       }
-
-       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-}
-
-static void
-o365_store_get_property (GObject *object,
-                        guint property_id,
-                        GValue *value,
-                        GParamSpec *pspec)
-{
-       switch (property_id) {
-               case PROP_CONNECTABLE:
-                       g_value_take_object (
-                               value,
-                               camel_network_service_ref_connectable (
-                                       CAMEL_NETWORK_SERVICE (object)));
-                       return;
-               case PROP_HOST_REACHABLE:
-                       g_value_set_boolean (
-                               value,
-                               camel_network_service_get_host_reachable (
-                                       CAMEL_NETWORK_SERVICE (object)));
-                       return;
-       }
-
-       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-}
-
 static gboolean
 o365_store_construct (CamelService *service,
                      CamelSession *session,
@@ -93,21 +61,21 @@ o365_store_construct (CamelService *service,
                      GError **error)
 {
        CamelO365Store *o365_store;
-       gchar /**summary_file,*/ *session_storage_path;
+       gchar *summary_file, *session_storage_path;
        guint32 store_flags;
+       GError *local_error = NULL;
 
        o365_store = (CamelO365Store *) service;
 
        store_flags = camel_store_get_flags (CAMEL_STORE (o365_store));
 
-       /* Disable virtual trash and junk folders. Office365 has real
-        * folders for that */
+       /* Disable virtual trash and junk folders. Office365 has real folders for that */
        store_flags &= ~(CAMEL_STORE_VTRASH | CAMEL_STORE_VJUNK);
        store_flags |= CAMEL_STORE_REAL_JUNK_FOLDER;
        camel_store_set_flags (CAMEL_STORE (o365_store), store_flags);
 
-       /*storage path*/
        session_storage_path = g_strdup (camel_service_get_user_cache_dir (service));
+
        if (!session_storage_path) {
                g_set_error (
                        error, CAMEL_STORE_ERROR,
@@ -119,11 +87,15 @@ o365_store_construct (CamelService *service,
        o365_store->priv->storage_path = session_storage_path;
 
        g_mkdir_with_parents (o365_store->priv->storage_path, 0700);
-       /*summary_file = g_build_filename (o365_store->storage_path, "folder-tree", NULL);
-       o365_store->summary = camel_o365_store_summary_new (summary_file);
-       camel_o365_store_summary_load (o365_store->summary, NULL);
 
-       g_free (summary_file);*/
+       summary_file = g_build_filename (o365_store->priv->storage_path, "folder-tree", NULL);
+       o365_store->priv->summary = camel_o365_store_summary_new (summary_file);
+
+       if (!camel_o365_store_summary_load (o365_store->priv->summary, &local_error))
+               g_warning ("%s: Failed to load store summary '%s': %s", G_STRFUNC, summary_file, local_error 
? local_error->message : "Unknown error");
+
+       g_clear_error (&local_error);
+       g_free (summary_file);
 
        return TRUE;
 }
@@ -183,12 +155,30 @@ o365_store_get_name (CamelService *service,
        return name;
 }
 
+static EO365Connection *
+o365_store_ref_connection (CamelO365Store *o365_store)
+{
+       EO365Connection *cnc = NULL;
+
+       g_return_val_if_fail (CAMEL_IS_O365_STORE (o365_store), NULL);
+
+       LOCK (o365_store);
+
+       if (o365_store->priv->cnc)
+               cnc = g_object_ref (o365_store->priv->cnc);
+
+       UNLOCK (o365_store);
+
+       return cnc;
+}
+
 static gboolean
 o365_store_connect_sync (CamelService *service,
                         GCancellable *cancellable,
                         GError **error)
 {
-       /*CamelO365Store *o365_store;*/
+       CamelO365Store *o365_store;
+       EO365Connection *cnc;
        gboolean success = FALSE;
 
        /* Chain up to parent's method. */
@@ -198,19 +188,29 @@ o365_store_connect_sync (CamelService *service,
        if (camel_service_get_connection_status (service) == CAMEL_SERVICE_DISCONNECTED)
                return FALSE;
 
-       /*o365_store = CAMEL_O365_STORE (service);
+       o365_store = CAMEL_O365_STORE (service);
+       cnc = o365_store_ref_connection (o365_store);
+
+       if (!cnc) {
+               LOCK (o365_store);
+
+               o365_store->priv->cnc = camel_o365_utils_new_connection (service, NULL);
+
+               UNLOCK (o365_store);
+       }
 
-       g_signal_connect_swapped (
-               o365_settings,
-               "notify::listen-notifications",
-               G_CALLBACK (camel_o365_store_listen_notifications_cb),
-               o365_store);
+       if (cnc) {
+               CamelSession *session;
 
-       g_signal_connect_swapped (
-               o365_settings,
-               "notify::check-all",
-               G_CALLBACK (camel_o365_store_check_all_cb),
-               o365_store);*/
+               session = camel_service_ref_session (service);
+
+               success = camel_session_authenticate_sync (session, service, "Office365", cancellable, error);
+
+               g_clear_object (&session);
+               g_clear_object (&cnc);
+       } else {
+               g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE, _("Failed 
to create connection"));
+       }
 
        return success;
 }
@@ -221,12 +221,22 @@ o365_store_disconnect_sync (CamelService *service,
                            GCancellable *cancellable,
                            GError **error)
 {
-       /*CamelO365Store *o365_store = CAMEL_O365_STORE (service);
+       CamelO365Store *o365_store = CAMEL_O365_STORE (service);
+       EO365Connection *cnc;
+       gboolean success = TRUE;
 
-       g_mutex_lock (&o365_store->priv->connection_lock);
-       o365_store_unset_connection_locked (o365_store, TRUE);
-       g_mutex_unlock (&o365_store->priv->connection_lock);*/
+       cnc = o365_store_ref_connection (o365_store);
+
+       if (cnc) {
+               success = e_o365_connection_disconnect_sync (cnc, cancellable, error);
+
+               g_clear_object (&cnc);
+       }
 
+       if (!success)
+               return FALSE;
+
+       /* Chain up to parent's method. */
        return CAMEL_SERVICE_CLASS (camel_o365_store_parent_class)->disconnect_sync (service, clean, 
cancellable, error);
 }
 
@@ -237,11 +247,29 @@ o365_store_authenticate_sync (CamelService *service,
                              GError **error)
 {
        CamelAuthenticationResult result;
-       /*CamelO365Store *o365_store;
-
-       o365_store = CAMEL_O365_STORE (service);*/
+       EO365Connection *cnc;
+
+       cnc = o365_store_ref_connection (CAMEL_O365_STORE (service));
+
+       if (!cnc)
+               return CAMEL_AUTHENTICATION_ERROR;
+
+       switch (e_o365_connection_authenticate_sync (cnc, cancellable, error)) {
+       case E_SOURCE_AUTHENTICATION_ERROR:
+       case E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED:
+       default:
+               result = CAMEL_AUTHENTICATION_ERROR;
+               break;
+       case E_SOURCE_AUTHENTICATION_ACCEPTED:
+               result = CAMEL_AUTHENTICATION_ACCEPTED;
+               break;
+       case E_SOURCE_AUTHENTICATION_REJECTED:
+       case E_SOURCE_AUTHENTICATION_REQUIRED:
+               result = CAMEL_AUTHENTICATION_REJECTED;
+               break;
+       }
 
-       result = CAMEL_AUTHENTICATION_ERROR;
+       g_clear_object (&cnc);
 
        return result;
 }
@@ -288,10 +316,296 @@ camel_o365_store_maybe_disconnect (CamelO365Store *store,
 #endif
 }
 
+static void
+o365_store_save_summary_locked (CamelO365StoreSummary *summary,
+                               const gchar *where)
+{
+       GError *error = NULL;
+
+       if (!camel_o365_store_summary_save (summary, &error))
+               g_warning ("%s: Failed to save store summary: %s", where, error ? error->message : "Unknown 
error");
+
+       g_clear_error (&error);
+}
+
+typedef struct _FolderRenamedData {
+       gchar *id;
+       gchar *old_name;
+} FolderRenamedData;
+
+static FolderRenamedData *
+folder_renamed_data_new (gchar *id,
+                        gchar *old_name)
+{
+       FolderRenamedData *frd;
+
+       frd = g_slice_new (FolderRenamedData);
+       frd->id = id;
+       frd->old_name = old_name;
+
+       return frd;
+}
+
+static void
+folder_renamed_data_free (gpointer ptr)
+{
+       FolderRenamedData *frd = ptr;
+
+       if (frd) {
+               g_free (frd->id);
+               g_free (frd->old_name);
+               g_slice_free (FolderRenamedData, frd);
+       }
+}
+
+typedef struct _FoldersDeltaData {
+       CamelO365Store *o365_store;
+       GSList *added_ids; /* gchar *, folder ids */
+       GSList *renamed_data; /* FolderRenamedData * */
+       GSList *removed_fis; /* CamelFolderInfo * */
+} FoldersDeltaData;
+
+static gboolean
+camel_o365_got_folders_delta_cb (EO365Connection *cnc,
+                                const GSList *results, /* JsonObject * - the returned objects from the 
server */
+                                gpointer user_data, /* expects GSList **, aka pointer to a GSList *, where 
it copies the 'results' */
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       FoldersDeltaData *fdd = user_data;
+       GSList *link;
+
+       g_return_val_if_fail (fdd, FALSE);
+
+       LOCK (fdd->o365_store);
+
+       for (link = (GSList *) results; link; link = g_slist_next (link)) {
+               JsonObject *object = link->data;
+               const gchar *id = e_o365_mail_folder_get_id (object);
+
+               if (e_o365_delta_is_removed_object (object)) {
+                       CamelFolderInfo *info;
+
+                       info = camel_o365_store_summary_build_folder_info_for_id 
(fdd->o365_store->priv->summary, id);
+
+                       if (info)
+                               fdd->removed_fis = g_slist_prepend (fdd->removed_fis, info);
+
+                       camel_o365_store_summary_remove_folder (fdd->o365_store->priv->summary, id);
+               } else {
+                       gchar *old_full_name = NULL;
+                       guint32 flags;
+
+                       if (camel_o365_store_summary_has_folder (fdd->o365_store->priv->summary, id))
+                               old_full_name = camel_o365_store_summary_dup_folder_full_name 
(fdd->o365_store->priv->summary, id);
+
+                       flags = e_o365_mail_folder_get_child_folder_count (object) ? 
CAMEL_STORE_INFO_FOLDER_CHILDREN : CAMEL_STORE_INFO_FOLDER_NOCHILDREN;
+
+                       camel_o365_store_summary_set_folder (fdd->o365_store->priv->summary, FALSE, id,
+                               e_o365_mail_folder_get_parent_folder_id (object),
+                               e_o365_mail_folder_get_display_name (object),
+                               e_o365_mail_folder_get_total_item_count (object),
+                               e_o365_mail_folder_get_unread_item_count (object),
+                               flags, E_O365_FOLDER_KIND_MAIL, FALSE, FALSE);
+
+                       if (old_full_name)
+                               fdd->renamed_data = g_slist_prepend (fdd->renamed_data, 
folder_renamed_data_new (g_strdup (id), old_full_name));
+                       else
+                               fdd->added_ids = g_slist_prepend (fdd->added_ids, g_strdup (id));
+               }
+       }
+
+       UNLOCK (fdd->o365_store);
+
+       return TRUE;
+}
+
+static CamelFolderInfo *
+o365_get_folder_info_sync (CamelStore *store,
+                          const gchar *top,
+                          guint32 flags,
+                          GCancellable *cancellable,
+                          GError **error)
+{
+       CamelO365Store *o365_store;
+       CamelFolderInfo *fi;
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (CAMEL_IS_O365_STORE (store), NULL);
+
+       o365_store = CAMEL_O365_STORE (store);
+
+       if (camel_offline_store_get_online (CAMEL_OFFLINE_STORE (o365_store))) {
+               gboolean refresh_online;
+
+               refresh_online = !(flags & CAMEL_STORE_FOLDER_INFO_FAST) ||
+                                 (flags & CAMEL_STORE_FOLDER_INFO_REFRESH) != 0;
+
+               if (!refresh_online) {
+                       gchar *delta_link;
+
+                       LOCK (o365_store);
+
+                       delta_link = camel_o365_store_summary_dup_delta_link (o365_store->priv->summary);
+                       refresh_online = !delta_link || !*delta_link;
+                       g_free (delta_link);
+
+                       UNLOCK (o365_store);
+               }
+
+               if (refresh_online) {
+                       EO365Connection *cnc;
+
+                       cnc = o365_store_ref_connection (o365_store);
+
+                       if (cnc) {
+                               FoldersDeltaData fdd;
+                               gchar *old_delta_link, *new_delta_link = NULL;
+
+                               LOCK (o365_store);
+
+                               old_delta_link = camel_o365_store_summary_dup_delta_link 
(o365_store->priv->summary);
+
+                               UNLOCK (o365_store);
+
+                               fdd.o365_store = o365_store;
+                               fdd.added_ids = NULL;
+                               fdd.renamed_data = NULL;
+                               fdd.removed_fis = NULL;
+
+                               success = e_o365_connection_get_mail_folders_delta_sync (cnc, NULL, NULL, 
old_delta_link, 0,
+                                       camel_o365_got_folders_delta_cb, &fdd, &new_delta_link, cancellable, 
error);
+
+                               if (success) {
+                                       CamelSubscribable *subscribable = CAMEL_SUBSCRIBABLE (o365_store);
+                                       CamelFolderInfo *info;
+                                       GSList *link;
+
+                                       LOCK (o365_store);
+
+                                       camel_o365_store_summary_set_delta_link (o365_store->priv->summary, 
new_delta_link);
+                                       o365_store_save_summary_locked (o365_store->priv->summary, G_STRFUNC);
+
+                                       fdd.added_ids = g_slist_reverse (fdd.added_ids);
+                                       fdd.renamed_data = g_slist_reverse (fdd.renamed_data);
+                                       fdd.removed_fis = g_slist_reverse (fdd.removed_fis);
+
+                                       if (fdd.added_ids || fdd.renamed_data || fdd.removed_fis)
+                                               camel_o365_store_summary_rebuild_hashes 
(o365_store->priv->summary);
+
+                                       for (link = fdd.removed_fis; link; link = g_slist_next (link)) {
+                                               info = link->data;
+
+                                               camel_subscribable_folder_unsubscribed (subscribable, info);
+                                               camel_store_folder_deleted (store, info);
+                                       }
+
+                                       for (link = fdd.added_ids; link; link = g_slist_next (link)) {
+                                               const gchar *id = link->data;
+
+                                               info = camel_o365_store_summary_build_folder_info_for_id 
(o365_store->priv->summary, id);
+
+                                               if (info) {
+                                                       camel_store_folder_created (store, info);
+                                                       camel_subscribable_folder_subscribed (subscribable, 
info);
+                                                       camel_folder_info_free (info);
+                                               }
+                                       }
+
+                                       for (link = fdd.renamed_data; link; link = g_slist_next (link)) {
+                                               const FolderRenamedData *frd = link->data;
+
+                                               info = camel_o365_store_summary_build_folder_info_for_id 
(o365_store->priv->summary, frd->id);
+
+                                               if (info) {
+                                                       camel_store_folder_renamed (store, frd->old_name, 
info);
+                                                       camel_folder_info_free (info);
+                                               }
+                                       }
+
+                                       UNLOCK (o365_store);
+                               }
+
+                               g_slist_free_full (fdd.added_ids, g_free);
+                               g_slist_free_full (fdd.renamed_data, folder_renamed_data_free);
+                               g_slist_free_full (fdd.removed_fis, (GDestroyNotify) camel_folder_info_free);
+
+                               g_clear_object (&cnc);
+                               g_free (old_delta_link);
+                               g_free (new_delta_link);
+                       }
+               }
+       }
+
+       if (success) {
+               LOCK (o365_store);
+
+               fi = camel_o365_store_summary_build_folder_info (o365_store->priv->summary, top, (flags & 
CAMEL_STORE_FOLDER_INFO_RECURSIVE) != 0);
+
+               UNLOCK (o365_store);
+       } else {
+               fi = NULL;
+       }
+
+       return fi;
+}
+
+static void
+o365_store_set_property (GObject *object,
+                        guint property_id,
+                        const GValue *value,
+                        GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_CONNECTABLE:
+                       camel_network_service_set_connectable (
+                               CAMEL_NETWORK_SERVICE (object),
+                               g_value_get_object (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+o365_store_get_property (GObject *object,
+                        guint property_id,
+                        GValue *value,
+                        GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_CONNECTABLE:
+                       g_value_take_object (
+                               value,
+                               camel_network_service_ref_connectable (
+                                       CAMEL_NETWORK_SERVICE (object)));
+                       return;
+               case PROP_HOST_REACHABLE:
+                       g_value_set_boolean (
+                               value,
+                               camel_network_service_get_host_reachable (
+                                       CAMEL_NETWORK_SERVICE (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
 static void
 o365_store_dispose (GObject *object)
 {
-       /*CamelO365Store *o365_store = CAMEL_O365_STORE (object);*/
+       CamelO365Store *o365_store = CAMEL_O365_STORE (object);
+
+       LOCK (o365_store);
+
+       if (o365_store->priv->summary) {
+               o365_store_save_summary_locked (o365_store->priv->summary, G_STRFUNC);
+               g_clear_object (&o365_store->priv->summary);
+       }
+
+       g_clear_object (&o365_store->priv->cnc);
+
+       UNLOCK (o365_store);
 
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (camel_o365_store_parent_class)->dispose (object);
@@ -315,7 +629,7 @@ camel_o365_store_class_init (CamelO365StoreClass *class)
 {
        GObjectClass *object_class;
        CamelServiceClass *service_class;
-       /*CamelStoreClass *store_class;*/
+       CamelStoreClass *store_class;
 
        object_class = G_OBJECT_CLASS (class);
        object_class->set_property = o365_store_set_property;
@@ -342,13 +656,16 @@ camel_o365_store_class_init (CamelO365StoreClass *class)
        service_class->connect_sync = o365_store_connect_sync;
        service_class->disconnect_sync = o365_store_disconnect_sync;
        service_class->authenticate_sync = o365_store_authenticate_sync;
-#if 0
+
        store_class = CAMEL_STORE_CLASS (class);
+#if 0
        store_class->get_folder_sync = o365_get_folder_sync;
        store_class->create_folder_sync = o365_create_folder_sync;
        store_class->delete_folder_sync = o365_delete_folder_sync;
        store_class->rename_folder_sync = o365_rename_folder_sync;
+#endif
        store_class->get_folder_info_sync = o365_get_folder_info_sync;
+#if 0
        store_class->initial_setup_sync = o365_initial_setup_sync;
        store_class->get_trash_folder_sync = o365_get_trash_folder_sync;
        store_class->get_junk_folder_sync = o365_get_junk_folder_sync;
diff --git a/src/Office365/camel/camel-o365-utils.c b/src/Office365/camel/camel-o365-utils.c
new file mode 100644
index 00000000..0d96872f
--- /dev/null
+++ b/src/Office365/camel/camel-o365-utils.c
@@ -0,0 +1,93 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2020 Red Hat (www.redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "evolution-ews-config.h"
+
+#include <libemail-engine/libemail-engine.h>
+
+#include "camel-o365-utils.h"
+
+/* Unref with g_object_unref() when done with it */
+static ESource *
+camel_o365_utils_ref_corresponding_source (CamelService *service,
+                                          GCancellable *cancellable)
+{
+       ESourceRegistry *registry = NULL;
+       CamelSession *session;
+       ESource *source = NULL;
+
+       g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+       session = camel_service_ref_session (service);
+       if (E_IS_MAIL_SESSION (session)) {
+               registry = e_mail_session_get_registry (E_MAIL_SESSION (session));
+               if (registry)
+                       g_object_ref (registry);
+       }
+
+       g_clear_object (&session);
+
+       if (!registry)
+               registry = e_source_registry_new_sync (cancellable, NULL);
+
+       if (registry) {
+               source = e_source_registry_ref_source (registry, camel_service_get_uid (service));
+
+               if (source) {
+                       ESource *parent;
+
+                       parent = e_source_registry_find_extension (registry, source, 
E_SOURCE_EXTENSION_COLLECTION);
+
+                       g_clear_object (&source);
+                       source = parent;
+               }
+       }
+
+       g_clear_object (&registry);
+
+       return source;
+}
+
+EO365Connection *
+camel_o365_utils_new_connection (CamelService *service,
+                                GCancellable *cancellable)
+{
+       CamelSettings *settings;
+       EO365Connection *cnc;
+       ESource *source;
+
+       g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+       source = camel_o365_utils_ref_corresponding_source (service, cancellable);
+
+       if (!source)
+               return NULL;
+
+       settings = camel_service_ref_settings (service);
+
+       cnc = e_o365_connection_new (source, CAMEL_O365_SETTINGS (settings));
+
+       e_binding_bind_property (
+               service, "proxy-resolver",
+               cnc, "proxy-resolver",
+               G_BINDING_SYNC_CREATE);
+
+       g_clear_object (&settings);
+       g_clear_object (&source);
+
+       return cnc;
+}
diff --git a/src/Office365/camel/camel-o365-utils.h b/src/Office365/camel/camel-o365-utils.h
new file mode 100644
index 00000000..dfca2254
--- /dev/null
+++ b/src/Office365/camel/camel-o365-utils.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2020 Red Hat (www.redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CAMEL_O365_UTILS_H
+#define CAMEL_O365_UTILS_H
+
+#include <camel/camel.h>
+
+#include "common/e-o365-connection.h"
+
+EO365Connection *
+               camel_o365_utils_new_connection (CamelService *service,
+                                                GCancellable *cancellable);
+
+#endif /* CAMEL_O365_UTILS_H */
diff --git a/src/Office365/common/e-o365-connection.c b/src/Office365/common/e-o365-connection.c
index ddb731b0..ca1fc975 100644
--- a/src/Office365/common/e-o365-connection.c
+++ b/src/Office365/common/e-o365-connection.c
@@ -45,6 +45,10 @@ struct _EO365ConnectionPrivate {
        GProxyResolver *proxy_resolver;
        ESoupAuthBearer *bearer_auth;
 
+       gboolean ssl_info_set;
+       gchar *ssl_certificate_pem;
+       GTlsCertificateFlags ssl_certificate_errors;
+
        gchar *hash_key; /* in the opened connections hash */
 
        /* How many microseconds to wait, until can execute a new request.
@@ -489,6 +493,7 @@ o365_connection_finalize (GObject *object)
        EO365Connection *cnc = E_O365_CONNECTION (object);
 
        g_rec_mutex_clear (&cnc->priv->property_lock);
+       g_clear_pointer (&cnc->priv->ssl_certificate_pem, g_free);
        g_free (cnc->priv->hash_key);
 
        /* Chain up to parent's method. */
@@ -827,6 +832,35 @@ o365_connection_request_cancelled_cb (GCancellable *cancellable,
        e_flag_set (flag);
 }
 
+static void
+o365_connection_extract_ssl_data (EO365Connection *cnc,
+                                 SoupMessage *message)
+{
+       GTlsCertificate *certificate = NULL;
+
+       g_return_if_fail (E_IS_O365_CONNECTION (cnc));
+       g_return_if_fail (SOUP_IS_MESSAGE (message));
+
+       LOCK (cnc);
+
+       g_clear_pointer (&cnc->priv->ssl_certificate_pem, g_free);
+       cnc->priv->ssl_info_set = FALSE;
+
+       g_object_get (G_OBJECT (message),
+               "tls-certificate", &certificate,
+               "tls-errors", &cnc->priv->ssl_certificate_errors,
+               NULL);
+
+       if (certificate) {
+               g_object_get (certificate, "certificate-pem", &cnc->priv->ssl_certificate_pem, NULL);
+               cnc->priv->ssl_info_set = TRUE;
+
+               g_object_unref (certificate);
+       }
+
+       UNLOCK (cnc);
+}
+
 /* An example error response:
 
   {
@@ -863,8 +897,10 @@ o365_connection_extract_error (JsonNode *node,
        if (!code && !message)
                return FALSE;
 
-       if (!status_code || !SOUP_STATUS_IS_SUCCESSFUL (status_code))
+       if (!status_code || SOUP_STATUS_IS_SUCCESSFUL (status_code))
                status_code = SOUP_STATUS_MALFORMED;
+       else if (g_strcmp0 (code, "ErrorInvalidUser") == 0)
+               status_code = SOUP_STATUS_UNAUTHORIZED;
 
        if (code && message)
                g_set_error (error, SOUP_HTTP_ERROR, status_code, "%s: %s", code, message);
@@ -974,6 +1010,10 @@ o365_connection_send_request_sync (EO365Connection *cnc,
 
                soup_session = cnc->priv->soup_session ? g_object_ref (cnc->priv->soup_session) : NULL;
 
+               g_clear_pointer (&cnc->priv->ssl_certificate_pem, g_free);
+               cnc->priv->ssl_certificate_errors = 0;
+               cnc->priv->ssl_info_set = FALSE;
+
                UNLOCK (cnc);
 
                if (soup_session &&
@@ -992,6 +1032,8 @@ o365_connection_send_request_sync (EO365Connection *cnc,
                            /* 
https://docs.microsoft.com/en-us/graph/best-practices-concept#handling-expected-errors */
                            message->status_code == SOUP_STATUS_SERVICE_UNAVAILABLE) {
                                need_retry = TRUE;
+                       } else if (message->status_code == SOUP_STATUS_SSL_FAILED) {
+                               o365_connection_extract_ssl_data (cnc, message);
                        }
 
                        if (need_retry) {
@@ -1204,6 +1246,8 @@ e_o365_construct_uri (EO365Connection *cnc,
                if (*name && value) {
                        g_string_append_c (uri, first_param ? '?' : '&');
 
+                       first_param = FALSE;
+
                        g_string_append (uri, name);
                        g_string_append_c (uri, '=');
 
@@ -1227,6 +1271,9 @@ e_o365_construct_uri (EO365Connection *cnc,
 }
 
 typedef struct _EO365ResponseData {
+       EO365ConnectionCallFunc func;
+       gpointer func_user_data;
+       gboolean read_only_once; /* To be able to just try authentication */
        GSList **out_items; /* JsonObject * */
        gchar **out_delta_link; /* set only if available and not NULL */
 } EO365ResponseData;
@@ -1245,6 +1292,8 @@ e_o365_read_valued_response_cb (EO365Connection *cnc,
        JsonObject *object;
        JsonArray *value;
        const gchar *delta_link;
+       GSList *items = NULL;
+       gboolean can_continue = TRUE;
        guint ii, len;
 
        g_return_val_if_fail (response_data != NULL, FALSE);
@@ -1254,7 +1303,8 @@ e_o365_read_valued_response_cb (EO365Connection *cnc,
        object = json_node_get_object (node);
        g_return_val_if_fail (object != NULL, FALSE);
 
-       *out_next_link = g_strdup (e_o365_json_get_string_member (object, "@odata.nextLink", NULL));
+       if (!response_data->read_only_once)
+               *out_next_link = g_strdup (e_o365_json_get_string_member (object, "@odata.nextLink", NULL));
 
        delta_link = e_o365_json_get_string_member (object, "@odata.deltaLink", NULL);
 
@@ -1274,11 +1324,160 @@ e_o365_read_valued_response_cb (EO365Connection *cnc,
                if (JSON_NODE_HOLDS_OBJECT (elem)) {
                        JsonObject *elem_object = json_node_get_object (elem);
 
-                       if (elem_object)
-                               *response_data->out_items = g_slist_prepend (*response_data->out_items, 
json_object_ref (elem_object));
+                       if (elem_object) {
+                               if (response_data->out_items)
+                                       *response_data->out_items = g_slist_prepend 
(*response_data->out_items, json_object_ref (elem_object));
+                               else
+                                       items = g_slist_prepend (items, json_object_ref (elem_object));
+                       }
                }
        }
 
+       if (response_data->func)
+               can_continue = response_data->func (cnc, items, response_data->func_user_data, cancellable, 
error);
+
+       g_slist_free_full (items, (GDestroyNotify) json_object_unref);
+
+       return can_continue;
+}
+
+gboolean
+e_o365_connection_get_ssl_error_details (EO365Connection *cnc,
+                                        gchar **out_certificate_pem,
+                                        GTlsCertificateFlags *out_certificate_errors)
+{
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+       g_return_val_if_fail (out_certificate_pem != NULL, FALSE);
+       g_return_val_if_fail (out_certificate_errors != NULL, FALSE);
+
+       LOCK (cnc);
+
+       if (!cnc->priv->ssl_info_set) {
+               UNLOCK (cnc);
+               return FALSE;
+       }
+
+       *out_certificate_pem = g_strdup (cnc->priv->ssl_certificate_pem);
+       *out_certificate_errors = cnc->priv->ssl_certificate_errors;
+
+       UNLOCK (cnc);
+
+       return TRUE;
+}
+
+ESourceAuthenticationResult
+e_o365_connection_authenticate_sync (EO365Connection *cnc,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+       ESourceAuthenticationResult result = E_SOURCE_AUTHENTICATION_ERROR;
+       EO365ResponseData rd;
+       SoupMessage *message;
+       gchar *uri;
+       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_construct_uri (cnc, TRUE, NULL, E_O365_API_V1_0, NULL,
+               "mailFolders",
+               NULL,
+               "$select", "displayName",
+               "$top", "1",
+               NULL);
+
+       message = soup_message_new (SOUP_METHOD_GET, uri);
+
+       if (!message) {
+               g_set_error (error, SOUP_HTTP_ERROR, SOUP_STATUS_MALFORMED, _("Malformed URI: ā€œ%sā€"), uri);
+               g_free (uri);
+
+               return FALSE;
+       }
+
+       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, &rd, 
cancellable, &local_error);
+
+       if (success) {
+               result = E_SOURCE_AUTHENTICATION_ACCEPTED;
+       } else {
+               if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED)) {
+                       local_error->domain = G_IO_ERROR;
+                       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;
+               } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED)) {
+                       ESoupAuthBearer *bearer;
+
+                       bearer = e_o365_connection_ref_bearer_auth (cnc);
+
+                       if (bearer)
+                               result = E_SOURCE_AUTHENTICATION_REJECTED;
+                       else
+                               result = E_SOURCE_AUTHENTICATION_REQUIRED;
+
+                       g_clear_object (&bearer);
+                       g_clear_error (&local_error);
+               }
+
+               if (local_error) {
+                       g_propagate_error (error, local_error);
+                       local_error = NULL;
+               }
+       }
+
+       g_slist_free_full (folders, (GDestroyNotify) json_object_unref);
+       g_clear_object (&message);
+       g_clear_error (&local_error);
+
+       return result;
+}
+
+gboolean
+e_o365_connection_disconnect_sync (EO365Connection *cnc,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
+
+       LOCK (cnc);
+
+       soup_session_abort (cnc->priv->soup_session);
+
+       UNLOCK (cnc);
+
+       return TRUE;
+}
+
+/* This can be used as a EO365ConnectionCallFunc function, it only
+   copies items of 'results' into 'user_data', which is supposed
+   to be a pointer to a GSList *. */
+gboolean
+e_o365_connection_call_gather_into_slist (EO365Connection *cnc,
+                                         const GSList *results, /* JsonObject * - the returned objects from 
the server */
+                                         gpointer user_data, /* expects GSList **, aka pointer to a GSList 
*, where it copies the 'results' */
+                                         GCancellable *cancellable,
+                                         GError **error)
+{
+       GSList **out_results = user_data, *link;
+
+       g_return_val_if_fail (out_results != NULL, FALSE);
+
+       for (link = (GSList *) results; link; link = g_slist_next (link)) {
+               JsonObject *obj = link->data;
+
+               if (obj)
+                       *out_results = g_slist_prepend (*out_results, json_object_ref (obj));
+       }
+
        return TRUE;
 }
 
@@ -1333,8 +1532,9 @@ e_o365_connection_get_mail_folders_delta_sync (EO365Connection *cnc,
                                               const gchar *select, /* fields to select, nullable */
                                               const gchar *delta_link, /* previous delta link */
                                               guint max_page_size, /* 0 for default by the server */
+                                              EO365ConnectionCallFunc func, /* function to call with each 
result set */
+                                              gpointer func_user_data, /* user data passed into the 'func' */
                                               gchar **out_delta_link,
-                                              GSList **out_folders, /* JsonObject * - the returned 
mailFolder objects */
                                               GCancellable *cancellable,
                                               GError **error)
 {
@@ -1344,7 +1544,7 @@ e_o365_connection_get_mail_folders_delta_sync (EO365Connection *cnc,
 
        g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
        g_return_val_if_fail (out_delta_link != NULL, FALSE);
-       g_return_val_if_fail (out_folders != NULL, FALSE);
+       g_return_val_if_fail (func != NULL, FALSE);
 
        if (delta_link)
                message = soup_message_new (SOUP_METHOD_GET, delta_link);
@@ -1382,7 +1582,8 @@ e_o365_connection_get_mail_folders_delta_sync (EO365Connection *cnc,
 
        memset (&rd, 0, sizeof (EO365ResponseData));
 
-       rd.out_items = out_folders;
+       rd.func = func;
+       rd.func_user_data = func_user_data;
        rd.out_delta_link = out_delta_link;
 
        success = o365_connection_send_request_sync (cnc, message, e_o365_read_valued_response_cb, &rd, 
cancellable, error);
diff --git a/src/Office365/common/e-o365-connection.h b/src/Office365/common/e-o365-connection.h
index a21a8558..aeba478f 100644
--- a/src/Office365/common/e-o365-connection.h
+++ b/src/Office365/common/e-o365-connection.h
@@ -51,6 +51,13 @@ typedef struct _EO365Connection EO365Connection;
 typedef struct _EO365ConnectionClass EO365ConnectionClass;
 typedef struct _EO365ConnectionPrivate EO365ConnectionPrivate;
 
+/* Returns whether can continue */
+typedef gboolean (* EO365ConnectionCallFunc)   (EO365Connection *cnc,
+                                                const GSList *results, /* JsonObject * - the returned 
objects from the server */
+                                                gpointer user_data,
+                                                GCancellable *cancellable,
+                                                GError **error);
+
 struct _EO365Connection {
        GObject parent;
        EO365ConnectionPrivate *priv;
@@ -94,6 +101,25 @@ ESoupAuthBearer *
 void           e_o365_connection_set_bearer_auth
                                                (EO365Connection *cnc,
                                                 ESoupAuthBearer *bearer_auth);
+gboolean       e_o365_connection_get_ssl_error_details
+                                               (EO365Connection *cnc,
+                                                gchar **out_certificate_pem,
+                                                GTlsCertificateFlags *out_certificate_errors);
+ESourceAuthenticationResult
+               e_o365_connection_authenticate_sync
+                                               (EO365Connection *cnc,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_disconnect_sync
+                                               (EO365Connection *cnc,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_o365_connection_call_gather_into_slist
+                                               (EO365Connection *cnc,
+                                                const GSList *results, /* JsonObject * - the returned 
objects from the server */
+                                                gpointer user_data, /* expects GSList **, aka pointer to a 
GSList *, where it copies the 'results' */
+                                                GCancellable *cancellable,
+                                                GError **error);
 gboolean       e_o365_connection_list_folders_sync
                                                (EO365Connection *cnc,
                                                 const gchar *user_override, /* for which user, NULL to use 
the account user */
@@ -108,8 +134,9 @@ gboolean    e_o365_connection_get_mail_folders_delta_sync
                                                 const gchar *select, /* fields to select, nullable */
                                                 const gchar *delta_link, /* previous delta link */
                                                 guint max_page_size, /* 0 for default by the server */
+                                                EO365ConnectionCallFunc func, /* function to call with each 
result set */
+                                                gpointer func_user_data, /* user data passed into the 'func' 
*/
                                                 gchar **out_delta_link,
-                                                GSList **out_folders, /* JsonObject * - the returned 
mailFolder objects */
                                                 GCancellable *cancellable,
                                                 GError **error);
 
diff --git a/src/Office365/common/e-o365-enums.h b/src/Office365/common/e-o365-enums.h
index b2718c74..130a289c 100644
--- a/src/Office365/common/e-o365-enums.h
+++ b/src/Office365/common/e-o365-enums.h
@@ -24,7 +24,7 @@ G_BEGIN_DECLS
 
 typedef enum {
        E_O365_FOLDER_KIND_UNKNOWN,
-       E_O365_FOLDER_KIND_MAILBOX,
+       E_O365_FOLDER_KIND_MAIL,
        E_O365_FOLDER_KIND_CALENDAR,
        E_O365_FOLDER_KIND_CONTACTS,
        E_O365_FOLDER_KIND_SEARCH,
diff --git a/src/Office365/evolution/e-mail-config-o365-backend.c 
b/src/Office365/evolution/e-mail-config-o365-backend.c
index 7faba8c0..eedad28a 100644
--- a/src/Office365/evolution/e-mail-config-o365-backend.c
+++ b/src/Office365/evolution/e-mail-config-o365-backend.c
@@ -103,7 +103,7 @@ test_clicked_cb (GtkButton *button,
        g_return_if_fail (cnc != NULL);
 
        //success = e_o365_connection_list_folders_sync (cnc, NULL, NULL, NULL, &folders, NULL, &error);
-       success = e_o365_connection_get_mail_folders_delta_sync (cnc, NULL, NULL, delta_link, 0, 
&new_delta_link, &folders, NULL, &error);
+       success = e_o365_connection_get_mail_folders_delta_sync (cnc, NULL, NULL, delta_link, 0, 
e_o365_connection_call_gather_into_slist, &folders, &new_delta_link, NULL, &error);
 
        if (success) {
                g_free (delta_link);


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