[evolution-ews/wip/mcrha/office365] Implement read of mail messages



commit 1fe26995cdfb7086838a025036182fdef152f84c
Author: Milan Crha <mcrha redhat com>
Date:   Wed Jun 24 19:46:55 2020 +0200

    Implement read of mail messages

 src/Office365/camel/camel-o365-folder.c            | 642 ++++++++++++++++++++-
 src/Office365/camel/camel-o365-store.c             | 274 ++++++++-
 src/Office365/camel/camel-o365-store.h             |   4 +-
 src/Office365/common/e-o365-connection.c           | 188 +++++-
 src/Office365/common/e-o365-connection.h           |  40 +-
 src/Office365/common/e-o365-json-utils.c           | 386 ++++++++++++-
 src/Office365/common/e-o365-json-utils.h           | 123 +++-
 .../evolution/e-mail-config-o365-backend.c         |   3 +-
 8 files changed, 1590 insertions(+), 70 deletions(-)
---
diff --git a/src/Office365/camel/camel-o365-folder.c b/src/Office365/camel/camel-o365-folder.c
index 7d4f9e42..b51382bc 100644
--- a/src/Office365/camel/camel-o365-folder.c
+++ b/src/Office365/camel/camel-o365-folder.c
@@ -32,6 +32,24 @@
 
 #define O365_LOCAL_CACHE_PATH "cur"
 
+/* https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0 */
+#define O365_FETCH_SUMMARY_PROPERTIES  "categories," \
+                                       "ccRecipients," \
+                                       "changeKey," \
+                                       "flag," \
+                                       "from," \
+                                       "hasAttachments," \
+                                       "id," \
+                                       "importance," \
+                                       "internetMessageHeaders," \
+                                       "internetMessageId," \
+                                       "isRead," \
+                                       "receivedDateTime," \
+                                       "sender," \
+                                       "sentDateTime," \
+                                       "subject," \
+                                       "toRecipients"
+
 #define LOCK_CACHE(_folder) g_rec_mutex_lock (&_folder->priv->cache_lock)
 #define UNLOCK_CACHE(_folder) g_rec_mutex_unlock (&_folder->priv->cache_lock)
 
@@ -44,6 +62,11 @@ struct _CamelO365FolderPrivate {
 
        GMutex search_lock;
        CamelFolderSearch *search;
+
+       /* To not download the same message multiple times from different threads */
+       GMutex get_message_lock;
+       GCond get_message_cond;
+       GHashTable *get_message_hash; /* borrowed gchar *uid ~> NULL */
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (CamelO365Folder, camel_o365_folder, CAMEL_TYPE_OFFLINE_FOLDER)
@@ -174,16 +197,6 @@ o365_folder_get_message_from_cache (CamelO365Folder *o365_folder,
        return msg;
 }
 
-static gchar *
-o365_folder_get_filename (CamelFolder *folder,
-                         const gchar *uid,
-                         GError **error)
-{
-       CamelO365Folder *o365_folder = CAMEL_O365_FOLDER (folder);
-
-       return o365_folder_cache_dup_filename (o365_folder, uid);
-}
-
 static void
 o365_folder_save_summary (CamelO365Folder *o365_folder)
 {
@@ -325,6 +338,603 @@ o365_folder_cmp_uids (CamelFolder *folder,
        return strcmp (uid1, uid2);
 }
 
+static gboolean
+o365_folder_download_message_cb (EO365Connection *cnc,
+                                SoupMessage *message,
+                                GInputStream *raw_data_stream,
+                                gpointer user_data,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       CamelStream *cache_stream = user_data;
+       gssize expected_size = 0, wrote_size = 0, last_percent = -1;
+       gint last_progress_notify = 0;
+       gsize buffer_size = 65535;
+       gchar *buffer;
+       gboolean success;
+
+       g_return_val_if_fail (CAMEL_IS_STREAM (cache_stream), FALSE);
+       g_return_val_if_fail (G_IS_INPUT_STREAM (raw_data_stream), FALSE);
+
+       if (message && message->response_headers) {
+               const gchar *content_length_str;
+
+               content_length_str = soup_message_headers_get_one (message->response_headers, 
"Content-Length");
+
+               if (content_length_str && *content_length_str)
+                       expected_size = (gssize) g_ascii_strtoll (content_length_str, NULL, 10);
+       }
+
+       buffer = g_malloc (buffer_size);
+
+       do {
+               success = !g_cancellable_set_error_if_cancelled (cancellable, error);
+
+               if (success) {
+                       gssize n_read, n_wrote;
+
+                       n_read = g_input_stream_read (raw_data_stream, buffer, buffer_size, cancellable, 
error);
+
+                       if (n_read == -1) {
+                               success = FALSE;
+                       } else if (!n_read) {
+                               break;
+                       } else {
+                               n_wrote = camel_stream_write (cache_stream, buffer, n_read, cancellable, 
error);
+                               success = n_read == n_wrote;
+
+                               if (success && expected_size > 0) {
+                                       gssize percent;
+
+                                       wrote_size += n_wrote;
+
+                                       percent = wrote_size * 100.0 / expected_size;
+
+                                       if (percent > 100)
+                                               percent = 100;
+
+                                       if (percent != last_percent) {
+                                               gint64 now = g_get_monotonic_time ();
+
+                                               /* Notify only 10 times per second, not more */
+                                               if (percent == 100 || now - last_progress_notify > 
G_USEC_PER_SEC / 10) {
+                                                       last_progress_notify = now;
+                                                       last_percent = percent;
+
+                                                       camel_operation_progress (cancellable, percent);
+                                               }
+                                       }
+                               }
+                       }
+               }
+       } while (success);
+
+       g_free (buffer);
+
+       if (success)
+               camel_stream_flush (cache_stream, cancellable, NULL);
+
+       return success;
+}
+
+static void
+o365_folder_get_message_cancelled_cb (GCancellable *cancellable,
+                                     gpointer user_data)
+{
+       CamelO365Folder *o365_folder = user_data;
+
+       g_return_if_fail (CAMEL_IS_O365_FOLDER (o365_folder));
+
+       g_mutex_lock (&o365_folder->priv->get_message_lock);
+       g_cond_broadcast (&o365_folder->priv->get_message_cond);
+       g_mutex_unlock (&o365_folder->priv->get_message_lock);
+}
+
+static CamelMimeMessage *
+o365_folder_get_message_sync (CamelFolder *folder,
+                             const gchar *uid,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       CamelMimeMessage *message = NULL;
+       CamelO365Folder *o365_folder;
+       CamelO365Store *o365_store;
+       CamelO365StoreSummary *o365_store_summary;
+       CamelStore *parent_store;
+       CamelStream *cache_stream = NULL;
+       EO365Connection *cnc = NULL;
+       GError *local_error = NULL;
+       gchar *folder_id;
+       gboolean success = TRUE, remove_from_hash = FALSE;
+
+       g_return_val_if_fail (CAMEL_IS_O365_FOLDER (folder), NULL);
+       g_return_val_if_fail (uid != NULL, NULL);
+
+       parent_store = camel_folder_get_parent_store (folder);
+
+       if (!parent_store)
+               return NULL;
+
+       o365_folder = CAMEL_O365_FOLDER (folder);
+       o365_store = CAMEL_O365_STORE (parent_store);
+
+       if (!camel_o365_store_ensure_connected (o365_store, &cnc, cancellable, error))
+               return NULL;
+
+       o365_store_summary = camel_o365_store_ref_store_summary (o365_store);
+
+       folder_id = camel_o365_store_summary_dup_folder_id_for_full_name (o365_store_summary,
+               camel_folder_get_full_name (folder));
+
+       if (!folder_id) {
+               g_set_error (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER, _("No such folder: %s"),
+                       camel_folder_get_full_name (folder));
+
+               g_clear_object (&o365_store_summary);
+               g_clear_object (&cnc);
+
+               return NULL;
+       }
+
+       g_mutex_lock (&o365_folder->priv->get_message_lock);
+
+       if (g_hash_table_contains (o365_folder->priv->get_message_hash, uid)) {
+               gulong handler_id = 0;
+
+               if (cancellable) {
+                       handler_id = g_signal_connect (cancellable, "cancelled",
+                               G_CALLBACK (o365_folder_get_message_cancelled_cb), o365_folder);
+               }
+
+               while (success = !g_cancellable_set_error_if_cancelled (cancellable, error),
+                      success && g_hash_table_contains (o365_folder->priv->get_message_hash, uid)) {
+                       g_cond_wait (&o365_folder->priv->get_message_cond, 
&o365_folder->priv->get_message_lock);
+               }
+
+               if (success)
+                       message = o365_folder_get_message_from_cache (o365_folder, uid, cancellable, NULL);
+
+               if (handler_id)
+                       g_signal_handler_disconnect (cancellable, handler_id);
+       }
+
+       if (success && !message) {
+               g_hash_table_insert (o365_folder->priv->get_message_hash, (gpointer) uid, NULL);
+               remove_from_hash = TRUE;
+       }
+
+       g_mutex_unlock (&o365_folder->priv->get_message_lock);
+
+       if (success && !message) {
+               cache_stream = o365_folder_cache_add (o365_folder, uid, error);
+
+               success = cache_stream != NULL;
+
+               success = success && e_o365_connection_get_mail_message_sync (cnc, NULL, folder_id, uid,
+                       o365_folder_download_message_cb, cache_stream, cancellable, &local_error);
+
+               if (local_error) {
+                       camel_o365_store_maybe_disconnect (o365_store, local_error);
+
+                       g_propagate_error (error, local_error);
+                       success = FALSE;
+               }
+
+               if (success) {
+                       /* First free the cache stream, thus the follwing call opens a new instance,
+                          which is rewinded at the beginning of the stream. */
+                       g_clear_object (&cache_stream);
+
+                       message = o365_folder_get_message_from_cache (o365_folder, uid, cancellable, error);
+               }
+       }
+
+       g_clear_object (&o365_store_summary);
+       g_clear_object (&cache_stream);
+       g_clear_object (&cnc);
+       g_free (folder_id);
+
+       if (remove_from_hash) {
+               g_mutex_lock (&o365_folder->priv->get_message_lock);
+               g_hash_table_remove (o365_folder->priv->get_message_hash, uid);
+               g_cond_broadcast (&o365_folder->priv->get_message_cond);
+               g_mutex_unlock (&o365_folder->priv->get_message_lock);
+       }
+
+       return message;
+}
+
+static gboolean
+o365_folder_update_message_info (CamelMessageInfo *mi,
+                                EO365MailMessage *mail)
+{
+       CamelO365MessageInfo *o365_mi;
+       guint32 flags = 0;
+       gboolean changed = FALSE;
+
+       g_return_val_if_fail (CAMEL_IS_O365_MESSAGE_INFO (mi), FALSE);
+       g_return_val_if_fail (mail != NULL, FALSE);
+
+       o365_mi = CAMEL_O365_MESSAGE_INFO (mi);
+
+       if (e_o365_mail_message_get_has_attachments (mail))
+               flags |= CAMEL_MESSAGE_ATTACHMENTS;
+
+       if (e_o365_mail_message_get_is_draft (mail))
+               flags |= CAMEL_MESSAGE_DRAFT;
+
+       if (e_o365_mail_message_get_is_read (mail))
+               flags |= CAMEL_MESSAGE_SEEN;
+
+       if (e_o365_mail_message_get_importance (mail) == E_O365_IMPORTANCE_HIGH)
+               flags |= CAMEL_MESSAGE_FLAGGED;
+
+       /* 2020-06-24 - cannot make it work, even with 
https://stackoverflow.com/questions/58205494/access-the-replied-forwarded-etc-state-from-rest */
+       /* CAMEL_MESSAGE_ANSWERED
+       CAMEL_MESSAGE_FORWARDED */
+
+       if (camel_o365_message_info_set_server_flags (o365_mi, flags)) {
+               guint32 mask;
+
+               mask = CAMEL_MESSAGE_ATTACHMENTS | CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN | 
CAMEL_MESSAGE_FLAGGED;
+
+               camel_message_info_set_flags (mi, mask, flags);
+
+               changed = TRUE;
+       }
+
+       return changed;
+}
+
+static gchar *
+o365_folder_recipients_as_string (JsonArray *recipients) /* EO365Recipient * */
+{
+       CamelInternetAddress *addrs;
+       guint ii, len;
+       gchar *res;
+
+       if (!recipients)
+               return NULL;
+
+       addrs = camel_internet_address_new ();
+
+       len = json_array_get_length (recipients);
+       for (ii = 0; ii < len; ii++) {
+               EO365Recipient *recipient = json_array_get_object_element (recipients, ii);
+               const gchar *name, *address;
+
+               name = e_o365_recipient_get_name (recipient);
+               address = e_o365_recipient_get_address (recipient);
+
+               if (address && *address)
+                       camel_internet_address_add (addrs, name, address);
+       }
+
+       if (camel_address_length (CAMEL_ADDRESS (addrs)) > 0) {
+               res = camel_address_format (CAMEL_ADDRESS (addrs));
+       } else {
+               res = NULL;
+       }
+
+       g_clear_object (&addrs);
+
+       return res;
+}
+
+static CamelMessageInfo *
+o365_folder_new_message_info_from_mail_message (CamelFolder *folder,
+                                               EO365MailMessage *mail)
+{
+       CamelMessageInfo *mi = NULL;
+       CamelNameValueArray *headers = NULL;
+       JsonArray *json_headers;
+
+       g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+       g_return_val_if_fail (mail != NULL, NULL);
+
+       json_headers = e_o365_mail_message_get_internet_message_headers (mail);
+
+       if (json_headers && json_array_get_length (json_headers) > 0) {
+               guint ii, len = json_array_get_length (json_headers);
+
+               headers = camel_name_value_array_new_sized (len);
+
+               for (ii = 0; ii < len; ii++) {
+                       EO365InternetMessageHeader *header = json_array_get_object_element (json_headers, ii);
+                       const gchar *name, *value;
+
+                       name = e_o365_internet_message_header_get_name (header);
+                       value = e_o365_internet_message_header_get_value (header);
+
+                       if (name && *name)
+                               camel_name_value_array_append (headers, name, value ? value : "");
+               }
+
+               if (camel_name_value_array_get_length (headers)) {
+                       mi = camel_message_info_new_from_headers (camel_folder_get_folder_summary (folder), 
headers);
+               } else {
+                       camel_name_value_array_free (headers);
+                       headers = NULL;
+               }
+       }
+
+       if (!mi) {
+               EO365Recipient *from;
+               const gchar *ctmp;
+               time_t tt;
+               gchar *tmp;
+
+               mi = camel_message_info_new (camel_folder_get_folder_summary (folder));
+
+               camel_message_info_set_abort_notifications (mi, TRUE);
+
+               ctmp = e_o365_mail_message_get_subject (mail);
+
+               if (ctmp)
+                       camel_message_info_set_subject  (mi, ctmp);
+
+               from = e_o365_mail_message_get_from (mail);
+
+               if (from) {
+                       const gchar *name, *address;
+
+                       name = e_o365_recipient_get_name (from);
+                       address = e_o365_recipient_get_address (from);
+
+                       if (address && *address) {
+                               tmp = camel_internet_address_format_address (name, address);
+
+                               if (tmp) {
+                                       camel_message_info_set_from (mi, tmp);
+
+                                       g_free (tmp);
+                               }
+                       }
+               }
+
+               tmp = o365_folder_recipients_as_string (e_o365_mail_message_get_to_recipients (mail));
+
+               if (tmp) {
+                       camel_message_info_set_to (mi, tmp);
+                       g_free (tmp);
+               }
+
+               tmp = o365_folder_recipients_as_string (e_o365_mail_message_get_cc_recipients (mail));
+
+               if (tmp) {
+                       camel_message_info_set_cc (mi, tmp);
+                       g_free (tmp);
+               }
+
+               tt = e_o365_mail_message_get_sent_date_time (mail);
+
+               if (tt)
+                       camel_message_info_set_date_sent (mi, (gint64) tt);
+
+               tt = e_o365_mail_message_get_received_date_time (mail);
+
+               if (tt)
+                       camel_message_info_set_date_received (mi, (gint64) tt);
+
+               ctmp = e_o365_mail_message_get_internet_message_id (mail);
+
+               if (ctmp && *ctmp) {
+                       GChecksum *checksum;
+                       CamelSummaryMessageID message_id;
+                       guint8 *digest;
+                       gsize length;
+
+                       length = g_checksum_type_get_length (G_CHECKSUM_MD5);
+                       digest = g_alloca (length);
+
+                       checksum = g_checksum_new (G_CHECKSUM_MD5);
+                       g_checksum_update (checksum, (const guchar *) ctmp, -1);
+                       g_checksum_get_digest (checksum, digest, &length);
+                       g_checksum_free (checksum);
+
+                       memcpy (message_id.id.hash, digest, sizeof (message_id.id.hash));
+
+                       camel_message_info_set_message_id (mi, message_id.id.id);
+               }
+
+               camel_message_info_set_abort_notifications (mi, FALSE);
+       }
+
+       camel_message_info_set_abort_notifications (mi, TRUE);
+       camel_message_info_set_uid (mi, e_o365_mail_message_get_id (mail));
+
+       if (headers)
+               camel_message_info_take_headers (mi, headers);
+
+       camel_message_info_set_abort_notifications (mi, FALSE);
+
+       o365_folder_update_message_info (mi, mail);
+
+       return mi;
+}
+
+typedef struct _SummaryDeltaData {
+       CamelFolder *folder;
+       CamelFolderChangeInfo *changes;
+       GList *removed_uids; /* gchar * - from the Camel string pool */
+} SummaryDeltaData;
+
+static gboolean
+o365_folder_got_summary_messages_cb (EO365Connection *cnc,
+                                    const GSList *results, /* JsonObject * - the returned objects from the 
server */
+                                    gpointer user_data,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+       SummaryDeltaData *sdd = user_data;
+       CamelFolderSummary *summary;
+       GSList *link;
+
+       g_return_val_if_fail (sdd != NULL, FALSE);
+
+       summary = camel_folder_get_folder_summary (sdd->folder);
+
+       if (!summary)
+               return FALSE;
+
+       for (link = (GSList *) results; link; link = g_slist_next (link)) {
+               EO365MailMessage *mail = link->data;
+               const gchar *id;
+
+               id = e_o365_mail_message_get_id (mail);
+
+               if (!id)
+                       continue;
+
+               if (!sdd->changes)
+                       sdd->changes = camel_folder_change_info_new ();
+
+               if (e_o365_delta_is_removed_object (mail)) {
+                       sdd->removed_uids = g_list_prepend (sdd->removed_uids, (gpointer) 
camel_pstring_strdup (id));
+
+                       camel_folder_change_info_remove_uid (sdd->changes, id);
+               } else {
+                       CamelMessageInfo *info;
+
+                       info = camel_folder_summary_get (summary, id);
+
+                       if (info) {
+                               if (o365_folder_update_message_info (info, mail))
+                                       camel_folder_change_info_change_uid (sdd->changes, id);
+
+                               g_object_unref (info);
+                       } else {
+                               info = o365_folder_new_message_info_from_mail_message (sdd->folder, mail);
+
+                               if (info) {
+                                       camel_folder_summary_add (summary, info, TRUE);
+
+                                       /* Unset folder-flagged flag, which ahd been set by the 
camel_folder_summary_add(),
+                                          to avoid re-sync on the just added message. */
+                                       camel_message_info_set_folder_flagged (info, FALSE);
+
+                                       camel_folder_change_info_add_uid (sdd->changes, id);
+                                       camel_folder_change_info_recent_uid (sdd->changes, id);
+
+                                       g_object_unref (info);
+                               }
+                       }
+               }
+       }
+
+       return TRUE;
+}
+
+static gboolean
+o365_folder_refresh_info_sync (CamelFolder *folder,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       CamelO365Folder *o365_folder;
+       CamelO365FolderSummary *o365_folder_summary;
+       CamelO365Store *o365_store;
+       CamelO365StoreSummary *o365_store_summary;
+       CamelFolderSummary *folder_summary;
+       CamelStore *parent_store;
+       EO365Connection *cnc = NULL;
+       SummaryDeltaData sdd;
+       GError *local_error = NULL;
+       gchar *folder_id, *curr_delta_link, *new_delta_link = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (CAMEL_IS_O365_FOLDER (folder), FALSE);
+
+       parent_store = camel_folder_get_parent_store (folder);
+
+       if (!parent_store)
+               return FALSE;
+
+       o365_folder = CAMEL_O365_FOLDER (folder);
+       o365_store = CAMEL_O365_STORE (parent_store);
+
+       if (!camel_o365_store_ensure_connected (o365_store, &cnc, cancellable, error))
+               return FALSE;
+
+       o365_store_summary = camel_o365_store_ref_store_summary (o365_store);
+
+       folder_id = camel_o365_store_summary_dup_folder_id_for_full_name (o365_store_summary,
+               camel_folder_get_full_name (folder));
+
+       if (!folder_id) {
+               g_set_error (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER, _("No such folder: %s"),
+                       camel_folder_get_full_name (folder));
+
+               g_clear_object (&o365_store_summary);
+               g_clear_object (&cnc);
+
+               return FALSE;
+       }
+
+       folder_summary = camel_folder_get_folder_summary (folder);
+       o365_folder_summary = CAMEL_O365_FOLDER_SUMMARY (folder_summary);
+
+       curr_delta_link = camel_o365_folder_summary_dup_delta_link (o365_folder_summary);
+
+       sdd.folder = 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,
+               curr_delta_link, 0, o365_folder_got_summary_messages_cb, &sdd,
+               &new_delta_link, cancellable, &local_error);
+
+       if (success && new_delta_link)
+               camel_o365_folder_summary_set_delta_link (o365_folder_summary, new_delta_link);
+
+       if (sdd.removed_uids) {
+               camel_folder_summary_remove_uids (folder_summary, sdd.removed_uids);
+
+               g_list_free_full (sdd.removed_uids, (GDestroyNotify) camel_pstring_free);
+       }
+
+       o365_folder_save_summary (o365_folder);
+
+       if (sdd.changes) {
+               if (camel_folder_change_info_changed (sdd.changes))
+                       camel_folder_changed (folder, sdd.changes);
+
+               camel_folder_change_info_free (sdd.changes);
+       }
+
+       if (local_error) {
+               camel_o365_store_maybe_disconnect (o365_store, local_error);
+
+               g_propagate_error (error, local_error);
+               success = FALSE;
+       }
+
+       g_clear_object (&o365_store_summary);
+       g_clear_object (&cnc);
+       g_free (curr_delta_link);
+       g_free (new_delta_link);
+       g_free (folder_id);
+
+       return success;
+}
+
+static void
+o365_folder_prepare_content_refresh (CamelFolder *folder)
+{
+       g_return_if_fail (CAMEL_IS_O365_FOLDER (folder));
+
+       camel_o365_folder_summary_set_delta_link (CAMEL_O365_FOLDER_SUMMARY (camel_folder_get_folder_summary 
(folder)), NULL);
+}
+
+static gchar *
+o365_folder_get_filename (CamelFolder *folder,
+                         const gchar *uid,
+                         GError **error)
+{
+       CamelO365Folder *o365_folder = CAMEL_O365_FOLDER (folder);
+
+       return o365_folder_cache_dup_filename (o365_folder, uid);
+}
+
 static void
 o365_folder_constructed (GObject *object)
 {
@@ -387,6 +997,10 @@ o365_folder_finalize (GObject *object)
 
        g_rec_mutex_clear (&o365_folder->priv->cache_lock);
        g_mutex_clear (&o365_folder->priv->search_lock);
+       g_mutex_clear (&o365_folder->priv->get_message_lock);
+       g_cond_clear (&o365_folder->priv->get_message_cond);
+
+       g_hash_table_destroy (o365_folder->priv->get_message_hash);
 
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (camel_o365_folder_parent_class)->finalize (object);
@@ -413,13 +1027,15 @@ camel_o365_folder_class_init (CamelO365FolderClass *klass)
        folder_class->cmp_uids = o365_folder_cmp_uids;
 #if 0
        folder_class->append_message_sync = o365_folder_append_message_sync;
+#endif
        folder_class->get_message_sync = o365_folder_get_message_sync;
        folder_class->refresh_info_sync = o365_folder_refresh_info_sync;
+#if 0
        folder_class->synchronize_sync = o365_folder_synchronize_sync;
        folder_class->expunge_sync = o365_folder_expunge_sync;
        folder_class->transfer_messages_to_sync = o365_folder_transfer_messages_to_sync;
-       folder_class->prepare_content_refresh = o365_folder_prepare_content_refresh;
 #endif
+       folder_class->prepare_content_refresh = o365_folder_prepare_content_refresh;
        folder_class->get_filename = o365_folder_get_filename;
 }
 
@@ -432,6 +1048,10 @@ camel_o365_folder_init (CamelO365Folder *o365_folder)
 
        g_rec_mutex_init (&o365_folder->priv->cache_lock);
        g_mutex_init (&o365_folder->priv->search_lock);
+       g_mutex_init (&o365_folder->priv->get_message_lock);
+       g_cond_init (&o365_folder->priv->get_message_cond);
+
+       o365_folder->priv->get_message_hash = g_hash_table_new (g_str_hash, g_str_equal);
 
        camel_folder_set_flags (folder, CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY);
        camel_folder_set_lock_async (folder, TRUE);
diff --git a/src/Office365/camel/camel-o365-store.c b/src/Office365/camel/camel-o365-store.c
index 65596703..4771eec4 100644
--- a/src/Office365/camel/camel-o365-store.c
+++ b/src/Office365/camel/camel-o365-store.c
@@ -22,7 +22,7 @@
 
 #include "common/camel-o365-settings.h"
 #include "common/e-o365-connection.h"
-#include "common/e-o365-json-utils.h"
+#include "camel-o365-folder.h"
 #include "camel-o365-store-summary.h"
 #include "camel-o365-utils.h"
 
@@ -198,6 +198,7 @@ o365_store_read_default_folders (CamelO365Store *o365_store,
 
                uri = e_o365_connection_construct_uri (cnc, TRUE, NULL, E_O365_API_V1_0, NULL,
                        "mailFolders",
+                       NULL,
                        default_folders[ii].name,
                        "$select", "id",
                        NULL);
@@ -364,6 +365,44 @@ o365_store_authenticate_sync (CamelService *service,
        return result;
 }
 
+static CamelFolder *
+o365_store_get_folder_sync (CamelStore *store,
+                           const gchar *folder_name,
+                           guint32 flags,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       CamelO365Store *o365_store;
+       CamelFolder *folder = NULL;
+       gchar *fid, *folder_dir, *display_name;
+
+       o365_store = CAMEL_O365_STORE (store);
+
+       fid = camel_o365_store_summary_dup_folder_id_for_full_name (o365_store->priv->summary, folder_name);
+
+       if (!fid) {
+               g_set_error (
+                       error, CAMEL_STORE_ERROR,
+                       CAMEL_STORE_ERROR_NO_FOLDER,
+                       _("No such folder: %s"), folder_name);
+               return NULL;
+       }
+
+       display_name = camel_o365_store_summary_dup_folder_display_name (o365_store->priv->summary, fid);
+       folder_dir = g_build_filename (o365_store->priv->storage_path, "folders", folder_name, NULL);
+
+       folder = camel_o365_folder_new (store, display_name, folder_name, folder_dir, cancellable, error);
+
+       g_free (display_name);
+       g_free (folder_dir);
+       g_free (fid);
+
+       if (folder && (flags & CAMEL_STORE_FOLDER_INFO_REFRESH) != 0)
+               camel_folder_prepare_content_refresh (folder);
+
+       return folder;
+}
+
 static void
 o365_store_save_summary_locked (CamelO365StoreSummary *summary,
                                const gchar *where)
@@ -600,6 +639,212 @@ o365_get_folder_info_sync (CamelStore *store,
        return fi;
 }
 
+/* Hold the property lock before calling this function */
+static void
+o365_store_save_setup_folder_locked (CamelO365Store *o365_store,
+                                    GHashTable *save_setup,
+                                    guint32 folder_type, /* one of TYPE constants from CamelFolderInfoFlags 
*/
+                                    const gchar *property_name)
+{
+       gchar *folder_id;
+
+       g_return_if_fail (CAMEL_IS_O365_STORE (o365_store));
+       g_return_if_fail (save_setup != NULL);
+       g_return_if_fail (folder_type != 0);
+       g_return_if_fail (property_name != NULL);
+
+       folder_id = camel_o365_store_summary_dup_folder_id_for_type (o365_store->priv->summary, folder_type);
+
+       if (folder_id) {
+               gchar *fullname;
+
+               fullname = camel_o365_store_summary_dup_folder_full_name (o365_store->priv->summary, 
folder_id);
+
+               if (fullname && *fullname) {
+                       g_hash_table_insert (save_setup,
+                               g_strdup (property_name),
+                               fullname);
+
+                       fullname = NULL;
+               }
+
+               g_free (fullname);
+               g_free (folder_id);
+       }
+}
+
+static gboolean
+o365_store_initial_setup_with_connection_sync (CamelStore *store,
+                                              GHashTable *save_setup,
+                                              EO365Connection *cnc,
+                                              GCancellable *cancellable,
+                                              GError **error)
+{
+       CamelO365Store *o365_store;
+
+       g_return_val_if_fail (CAMEL_IS_O365_STORE (store), FALSE);
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return FALSE;
+
+       o365_store = CAMEL_O365_STORE (store);
+
+       if (cnc) {
+               g_object_ref (cnc);
+       } else {
+               if (!camel_o365_store_ensure_connected (o365_store, &cnc, cancellable, error))
+                       return FALSE;
+
+               g_return_val_if_fail (cnc != NULL, FALSE);
+       }
+
+       if (!o365_store_read_default_folders (o365_store, cnc, cancellable, error)) {
+               g_clear_object (&cnc);
+               return FALSE;
+       }
+
+       if (save_setup) {
+               LOCK (o365_store);
+
+               o365_store_save_setup_folder_locked (o365_store, save_setup, CAMEL_FOLDER_TYPE_SENT, 
CAMEL_STORE_SETUP_SENT_FOLDER);
+               o365_store_save_setup_folder_locked (o365_store, save_setup, CAMEL_FOLDER_TYPE_DRAFTS, 
CAMEL_STORE_SETUP_DRAFTS_FOLDER);
+               o365_store_save_setup_folder_locked (o365_store, save_setup, CAMEL_FOLDER_TYPE_ARCHIVE, 
CAMEL_STORE_SETUP_ARCHIVE_FOLDER);
+
+               UNLOCK (o365_store);
+       }
+
+       g_clear_object (&cnc);
+
+       return TRUE;
+}
+
+static gboolean
+o365_store_initial_setup_sync (CamelStore *store,
+                              GHashTable *save_setup,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       return o365_store_initial_setup_with_connection_sync (store, save_setup, NULL, cancellable, error);
+}
+
+static CamelFolder *
+o365_store_get_trash_folder_sync (CamelStore *store,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       CamelO365Store *o365_store;
+       CamelFolder *folder = NULL;
+       gchar *folder_id, *folder_name;
+
+       g_return_val_if_fail (CAMEL_IS_O365_STORE (store), NULL);
+
+       o365_store = CAMEL_O365_STORE (store);
+
+       LOCK (o365_store);
+
+       folder_id = camel_o365_store_summary_dup_folder_id_for_type (o365_store->priv->summary, 
CAMEL_FOLDER_TYPE_TRASH);
+
+       if (!folder_id) {
+               UNLOCK (o365_store);
+               g_set_error_literal (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER, _("Could not 
locate Trash folder"));
+               return NULL;
+       }
+
+       folder_name = camel_o365_store_summary_dup_folder_full_name (o365_store->priv->summary, folder_id);
+
+       UNLOCK (o365_store);
+
+       folder = camel_store_get_folder_sync (store, folder_name, 0, cancellable, error);
+
+       g_free (folder_name);
+       g_free (folder_id);
+
+       if (folder) {
+               GPtrArray *folders;
+               gboolean can = TRUE;
+               guint ii;
+
+               /* Save content of all opened folders, thus any messages deleted in them
+                  are moved to the Deleted Items folder first, thus in case of the trash
+                  folder instance being used to expunge messages will contain all of them.
+               */
+               folders = camel_store_dup_opened_folders (store);
+
+               for (ii = 0; ii < folders->len; ii++) {
+                       CamelFolder *secfolder = folders->pdata[ii];
+
+                       if (secfolder != folder && can)
+                               can = camel_folder_synchronize_sync (secfolder, FALSE, cancellable, NULL);
+
+                       g_object_unref (secfolder);
+               }
+               g_ptr_array_free (folders, TRUE);
+
+               /* To return 'Deleted Items' folder with current content,
+                  not with possibly stale locally cached copy. */
+               camel_folder_refresh_info_sync (folder, cancellable, NULL);
+       }
+
+       return folder;
+}
+
+static CamelFolder *
+o365_store_get_junk_folder_sync (CamelStore *store,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       CamelO365Store *o365_store;
+       CamelFolder *folder = NULL;
+       gchar *folder_id, *folder_name;
+
+       g_return_val_if_fail (CAMEL_IS_O365_STORE (store), NULL);
+
+       o365_store = CAMEL_O365_STORE (store);
+
+       folder_id = camel_o365_store_summary_dup_folder_id_for_type (o365_store->priv->summary, 
CAMEL_FOLDER_TYPE_JUNK);
+
+       if (!folder_id) {
+               g_set_error_literal (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER, _("Could not 
locate Junk folder"));
+               return NULL;
+       }
+
+       folder_name = camel_o365_store_summary_dup_folder_full_name (o365_store->priv->summary, folder_id);
+
+       folder = camel_store_get_folder_sync (store, folder_name, 0, cancellable, error);
+
+       g_free (folder_name);
+       g_free (folder_id);
+
+       return folder;
+}
+
+static gboolean
+o365_store_can_refresh_folder (CamelStore *store,
+                              CamelFolderInfo *info,
+                              GError **error)
+{
+       CamelSettings *settings;
+       CamelO365Settings *o365_settings;
+       gboolean check_all;
+
+       /* Skip unselectable folders from automatic refresh */
+       if (info && (info->flags & CAMEL_FOLDER_NOSELECT) != 0)
+               return FALSE;
+
+       settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+
+       o365_settings = CAMEL_O365_SETTINGS (settings);
+       check_all = camel_o365_settings_get_check_all (o365_settings);
+
+       g_object_unref (settings);
+
+       if (check_all)
+               return TRUE;
+
+       /* Delegate decision to parent class */
+       return CAMEL_STORE_CLASS (camel_o365_store_parent_class)->can_refresh_folder (store, info, error);
+}
+
 static void
 o365_store_set_property (GObject *object,
                         guint property_id,
@@ -710,19 +955,17 @@ camel_o365_store_class_init (CamelO365StoreClass *class)
        service_class->authenticate_sync = o365_store_authenticate_sync;
 
        store_class = CAMEL_STORE_CLASS (class);
-#if 0
        store_class->get_folder_sync = o365_store_get_folder_sync;
+#if 0
        store_class->create_folder_sync = o365_store_create_folder_sync;
        store_class->delete_folder_sync = o365_store_delete_folder_sync;
        store_class->rename_folder_sync = o365_store_rename_folder_sync;
 #endif
        store_class->get_folder_info_sync = o365_get_folder_info_sync;
-#if 0
        store_class->initial_setup_sync = o365_store_initial_setup_sync;
        store_class->get_trash_folder_sync = o365_store_get_trash_folder_sync;
        store_class->get_junk_folder_sync = o365_store_get_junk_folder_sync;
        store_class->can_refresh_folder = o365_store_can_refresh_folder;
-#endif
 }
 
 static void
@@ -789,23 +1032,34 @@ camel_o365_store_ref_connection (CamelO365Store *o365_store)
 }
 
 gboolean
-camel_o365_store_connected (CamelO365Store *o365_store,
-                           GCancellable *cancellable,
-                           GError **error)
+camel_o365_store_ensure_connected (CamelO365Store *o365_store,
+                                  EO365Connection **out_cnc, /* out, nullable, transfer full */
+                                  GCancellable *cancellable,
+                                  GError **error)
 {
        g_return_val_if_fail (CAMEL_IS_O365_STORE (o365_store), FALSE);
 
        if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (o365_store))) {
-               g_set_error (
-                       error, CAMEL_SERVICE_ERROR,
-                       CAMEL_SERVICE_ERROR_UNAVAILABLE,
+               g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE,
                        _("You must be working online to complete this operation"));
+
                return FALSE;
        }
 
        if (!camel_service_connect_sync ((CamelService *) o365_store, cancellable, error))
                return FALSE;
 
+       if (out_cnc) {
+               *out_cnc = camel_o365_store_ref_connection (o365_store);
+
+               if (!*out_cnc) {
+                       g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE,
+                               _("You must be working online to complete this operation"));
+
+                       return FALSE;
+               }
+       }
+
        return TRUE;
 }
 
diff --git a/src/Office365/camel/camel-o365-store.h b/src/Office365/camel/camel-o365-store.h
index af42cd33..eb6ad5d2 100644
--- a/src/Office365/camel/camel-o365-store.h
+++ b/src/Office365/camel/camel-o365-store.h
@@ -64,7 +64,9 @@ CamelO365StoreSummary *
                                                (CamelO365Store *store);
 EO365Connection *
                camel_o365_store_ref_connection (CamelO365Store *o365_store);
-gboolean       camel_o365_store_connected      (CamelO365Store *store,
+gboolean       camel_o365_store_ensure_connected
+                                               (CamelO365Store *store,
+                                                EO365Connection **out_cnc, /* out, nullable, trasnfer full */
                                                 GCancellable *cancellable,
                                                 GError **error);
 void           camel_o365_store_maybe_disconnect
diff --git a/src/Office365/common/e-o365-connection.c b/src/Office365/common/e-o365-connection.c
index c30ba256..e1855790 100644
--- a/src/Office365/common/e-o365-connection.c
+++ b/src/Office365/common/e-o365-connection.c
@@ -1124,7 +1124,8 @@ e_o365_connection_json_node_from_message (SoupMessage *message,
 static gboolean
 o365_connection_send_request_sync (EO365Connection *cnc,
                                   SoupMessage *message,
-                                  EO365ResponseFunc func,
+                                  EO365ResponseFunc response_func,
+                                  EO365ConnectionRawDataFunc raw_data_func,
                                   gpointer func_user_data,
                                   GCancellable *cancellable,
                                   GError **error)
@@ -1135,7 +1136,8 @@ o365_connection_send_request_sync (EO365Connection *cnc,
 
        g_return_val_if_fail (E_IS_O365_CONNECTION (cnc), FALSE);
        g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
-       g_return_val_if_fail (func != NULL, FALSE);
+       g_return_val_if_fail (response_func != NULL || raw_data_func != NULL, FALSE);
+       g_return_val_if_fail (response_func == NULL || raw_data_func == NULL, FALSE);
 
        while (need_retry && !g_cancellable_is_cancelled (cancellable) && message->status_code != 
SOUP_STATUS_CANCELLED) {
                need_retry = FALSE;
@@ -1265,6 +1267,8 @@ o365_connection_send_request_sync (EO365Connection *cnc,
                                UNLOCK (cnc);
 
                                success = FALSE;
+                       } else if (success && raw_data_func && SOUP_STATUS_IS_SUCCESSFUL 
(message->status_code)) {
+                               success = raw_data_func (cnc, message, input_stream, func_user_data, 
cancellable, error);
                        } else if (success) {
                                JsonNode *node = NULL;
 
@@ -1273,7 +1277,7 @@ o365_connection_send_request_sync (EO365Connection *cnc,
                                if (success) {
                                        gchar *next_link = NULL;
 
-                                       success = func (cnc, message, input_stream, node, func_user_data, 
&next_link, cancellable, error);
+                                       success = response_func && response_func (cnc, message, input_stream, 
node, func_user_data, &next_link, cancellable, error);
 
                                        if (success && next_link && *next_link) {
                                                SoupURI *suri;
@@ -1332,7 +1336,7 @@ o365_connection_send_request_sync (EO365Connection *cnc,
 }
 
 typedef struct _EO365ResponseData {
-       EO365ConnectionCallFunc func;
+       EO365ConnectionJsonFunc json_func;
        gpointer func_user_data;
        gboolean read_only_once; /* To be able to just try authentication */
        GSList **out_items; /* JsonObject * */
@@ -1395,8 +1399,8 @@ e_o365_read_valued_response_cb (EO365Connection *cnc,
                }
        }
 
-       if (response_data->func)
-               can_continue = response_data->func (cnc, items, response_data->func_user_data, cancellable, 
error);
+       if (response_data->json_func)
+               can_continue = response_data->json_func (cnc, items, response_data->func_user_data, 
cancellable, error);
 
        g_slist_free_full (items, (GDestroyNotify) json_object_unref);
 
@@ -1446,6 +1450,7 @@ e_o365_connection_authenticate_sync (EO365Connection *cnc,
        uri = e_o365_connection_construct_uri (cnc, TRUE, NULL, E_O365_API_V1_0, NULL,
                "mailFolders",
                NULL,
+               NULL,
                "$select", "displayName",
                "$top", "1",
                NULL);
@@ -1466,7 +1471,7 @@ e_o365_connection_authenticate_sync (EO365Connection *cnc,
        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);
+       success = o365_connection_send_request_sync (cnc, message, e_o365_read_valued_response_cb, NULL, &rd, 
cancellable, &local_error);
 
        if (success) {
                result = E_SOURCE_AUTHENTICATION_ACCEPTED;
@@ -1529,7 +1534,8 @@ e_o365_connection_disconnect_sync (EO365Connection *cnc,
        return TRUE;
 }
 
-/* Expects NULL-terminated pair of parameters 'name', 'value'; if 'value' is NULL, the parameter is skipped 
*/
+/* Expects NULL-terminated pair of parameters 'name', 'value'; if 'value' is NULL, the parameter is skipped.
+   An empty 'name' can add the 'value' into the path. These can be only before query parameters. */
 gchar *
 e_o365_connection_construct_uri (EO365Connection *cnc,
                                 gboolean include_user,
@@ -1537,6 +1543,7 @@ e_o365_connection_construct_uri (EO365Connection *cnc,
                                 EO365ApiVersion api_version,
                                 const gchar *api_part,
                                 const gchar *resource,
+                                const gchar *id,
                                 const gchar *path,
                                 ...)
 {
@@ -1603,6 +1610,11 @@ e_o365_connection_construct_uri (EO365Connection *cnc,
                g_string_append (uri, resource);
        }
 
+       if (id && *id) {
+               g_string_append_c (uri, '/');
+               g_string_append (uri, id);
+       }
+
        if (path && *path) {
                g_string_append_c (uri, '/');
                g_string_append (uri, path);
@@ -1632,6 +1644,12 @@ e_o365_connection_construct_uri (EO365Connection *cnc,
 
                                g_free (encoded);
                        }
+               } else if (!*name && value && *value) {
+                       /* Warn when adding path after additional query parameters */
+                       g_warn_if_fail (first_param);
+
+                       g_string_append_c (uri, '/');
+                       g_string_append (uri, value);
                }
 
                name = va_arg (args, const gchar *);
@@ -1767,7 +1785,7 @@ e_o365_connection_batch_request_internal_sync (EO365Connection *cnc,
        g_return_val_if_fail (requests->len <= E_O365_BATCH_MAX_REQUESTS, FALSE);
 
        uri = e_o365_connection_construct_uri (cnc, FALSE, NULL, api_version, "",
-               "$batch", NULL, NULL);
+               "$batch", NULL, NULL, NULL);
 
        message = soup_message_new (SOUP_METHOD_POST, uri);
 
@@ -1887,7 +1905,7 @@ e_o365_connection_batch_request_internal_sync (EO365Connection *cnc,
        g_object_unref (builder);
        g_object_unref (generator);
 
-       success = o365_connection_send_request_sync (cnc, message, e_o365_read_batch_response_cb, requests, 
cancellable, error);
+       success = o365_connection_send_request_sync (cnc, message, e_o365_read_batch_response_cb, NULL, 
requests, cancellable, error);
 
        g_clear_object (&message);
 
@@ -1987,7 +2005,7 @@ e_o365_connection_batch_request_sync (EO365Connection *cnc,
        return success;
 }
 
-/* This can be used as a EO365ConnectionCallFunc function, it only
+/* This can be used as a EO365ConnectionJsonFunc function, it only
    copies items of 'results' into 'user_data', which is supposed
    to be a pointer to a GSList *. */
 gboolean
@@ -2011,14 +2029,16 @@ e_o365_connection_call_gather_into_slist (EO365Connection *cnc,
        return TRUE;
 }
 
+/* https://docs.microsoft.com/en-us/graph/api/user-list-mailfolders?view=graph-rest-1.0&tabs=http */
+
 gboolean
-e_o365_connection_list_folders_sync (EO365Connection *cnc,
-                                    const gchar *user_override, /* for which user, NULL to use the account 
user */
-                                    const gchar *from_path, /* path for the folder to read, NULL for top 
user folder */
-                                    const gchar *select, /* fields to select, nullable */
-                                    GSList **out_folders, /* JsonObject * - the returned mailFolder objects 
*/
-                                    GCancellable *cancellable,
-                                    GError **error)
+e_o365_connection_list_mail_folders_sync (EO365Connection *cnc,
+                                         const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                         const gchar *from_path, /* path for the folder to read, NULL for 
top user folder */
+                                         const gchar *select, /* properties to select, nullable */
+                                         GSList **out_folders, /* JsonObject * - the returned mailFolder 
objects */
+                                         GCancellable *cancellable,
+                                         GError **error)
 {
        EO365ResponseData rd;
        SoupMessage *message;
@@ -2030,6 +2050,7 @@ e_o365_connection_list_folders_sync (EO365Connection *cnc,
 
        uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
                "mailFolders",
+               NULL,
                from_path,
                "$select", select,
                NULL);
@@ -2049,7 +2070,7 @@ e_o365_connection_list_folders_sync (EO365Connection *cnc,
 
        rd.out_items = out_folders;
 
-       success = o365_connection_send_request_sync (cnc, message, e_o365_read_valued_response_cb, &rd, 
cancellable, error);
+       success = o365_connection_send_request_sync (cnc, message, e_o365_read_valued_response_cb, NULL, &rd, 
cancellable, error);
 
        g_clear_object (&message);
 
@@ -2059,10 +2080,10 @@ e_o365_connection_list_folders_sync (EO365Connection *cnc,
 gboolean
 e_o365_connection_get_mail_folders_delta_sync (EO365Connection *cnc,
                                               const gchar *user_override, /* for which user, NULL to use the 
account user */
-                                              const gchar *select, /* fields to select, nullable */
+                                              const gchar *select, /* properties 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 */
+                                              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,
@@ -2084,6 +2105,7 @@ e_o365_connection_get_mail_folders_delta_sync (EO365Connection *cnc,
 
                uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
                        "mailFolders",
+                       NULL,
                        "delta",
                        "$select", select,
                        NULL);
@@ -2112,11 +2134,131 @@ e_o365_connection_get_mail_folders_delta_sync (EO365Connection *cnc,
 
        memset (&rd, 0, sizeof (EO365ResponseData));
 
-       rd.func = func;
+       rd.json_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);
+       success = o365_connection_send_request_sync (cnc, message, e_o365_read_valued_response_cb, NULL, &rd, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/message-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)
+{
+       EO365ResponseData rd;
+       SoupMessage *message = NULL;
+       gboolean success;
+
+       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 (out_delta_link != NULL, FALSE);
+       g_return_val_if_fail (func != NULL, FALSE);
+
+       if (delta_link)
+               message = soup_message_new (SOUP_METHOD_GET, delta_link);
+
+       if (!message) {
+               gchar *uri;
+
+               uri = e_o365_connection_construct_uri (cnc, TRUE, user_override, E_O365_API_V1_0, NULL,
+                       "mailFolders",
+                       folder_id,
+                       "messages",
+                       "", "delta",
+                       "$select", select,
+                       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);
+       }
+
+       if (max_page_size > 0) {
+               gchar *prefer_value;
+
+               prefer_value = g_strdup_printf ("odata.maxpagesize=%u", max_page_size);
+
+               soup_message_headers_append (message->request_headers, "Prefer", prefer_value);
+
+               g_free (prefer_value);
+       }
+
+       memset (&rd, 0, sizeof (EO365ResponseData));
+
+       rd.json_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, NULL, &rd, 
cancellable, error);
+
+       g_clear_object (&message);
+
+       return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/message-get?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_o365_connection_get_mail_message_sync (EO365Connection *cnc,
+                                        const gchar *user_override, /* for which user, NULL to use the 
account user */
+                                        const gchar *folder_id,
+                                        const gchar *message_id,
+                                        EO365ConnectionRawDataFunc func,
+                                        gpointer func_user_data,
+                                        GCancellable *cancellable,
+                                        GError **error)
+{
+       SoupMessage *message = NULL;
+       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 (message_id != NULL, FALSE);
+       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",
+               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);
+
+       success = o365_connection_send_request_sync (cnc, message, NULL, func, func_user_data, cancellable, 
error);
 
        g_clear_object (&message);
 
diff --git a/src/Office365/common/e-o365-connection.h b/src/Office365/common/e-o365-connection.h
index df6a8ae9..9854e02f 100644
--- a/src/Office365/common/e-o365-connection.h
+++ b/src/Office365/common/e-o365-connection.h
@@ -26,6 +26,7 @@
 
 #include "camel-o365-settings.h"
 #include "e-o365-enums.h"
+#include "e-o365-json-utils.h"
 
 /* Currently, as of 2020-06-17, there is a limitation to 20 requests:
    https://docs.microsoft.com/en-us/graph/known-issues#json-batching */
@@ -62,12 +63,19 @@ typedef struct _EO365ConnectionClass EO365ConnectionClass;
 typedef struct _EO365ConnectionPrivate EO365ConnectionPrivate;
 
 /* Returns whether can continue */
-typedef gboolean (* EO365ConnectionCallFunc)   (EO365Connection *cnc,
+typedef gboolean (* EO365ConnectionJsonFunc)   (EO365Connection *cnc,
                                                 const GSList *results, /* JsonObject * - the returned 
objects from the server */
                                                 gpointer user_data,
                                                 GCancellable *cancellable,
                                                 GError **error);
 
+typedef gboolean (* EO365ConnectionRawDataFunc)        (EO365Connection *cnc,
+                                                SoupMessage *message,
+                                                GInputStream *raw_data_stream,
+                                                gpointer user_data,
+                                                GCancellable *cancellable,
+                                                GError **error);
+
 struct _EO365Connection {
        GObject parent;
        EO365ConnectionPrivate *priv;
@@ -130,6 +138,7 @@ gchar *             e_o365_connection_construct_uri (EO365Connection *cnc,
                                                 EO365ApiVersion api_version,
                                                 const gchar *api_part, /* NULL for 'users', empty string to 
skip */
                                                 const gchar *resource,
+                                                const gchar *id, /* NULL to skip */
                                                 const gchar *path,
                                                 ...) G_GNUC_NULL_TERMINATED;
 gboolean       e_o365_connection_json_node_from_message
@@ -150,25 +159,46 @@ gboolean  e_o365_connection_call_gather_into_slist
                                                 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
+gboolean       e_o365_connection_list_mail_folders_sync
                                                (EO365Connection *cnc,
                                                 const gchar *user_override, /* for which user, NULL to use 
the account user */
                                                 const gchar *from_path, /* path for the folder to read, NULL 
for top user folder */
-                                                const gchar *select, /* fields to select, nullable */
+                                                const gchar *select, /* properties to select, nullable */
                                                 GSList **out_folders, /* JsonObject * - the returned 
mailFolder objects */
                                                 GCancellable *cancellable,
                                                 GError **error);
 gboolean       e_o365_connection_get_mail_folders_delta_sync
                                                (EO365Connection *cnc,
                                                 const gchar *user_override, /* for which user, NULL to use 
the account user */
-                                                const gchar *select, /* fields to select, nullable */
+                                                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);
+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 */
-                                                EO365ConnectionCallFunc func, /* function to call with each 
result set */
+                                                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);
+gboolean       e_o365_connection_get_mail_message_sync
+                                               (EO365Connection *cnc,
+                                                const gchar *user_override, /* for which user, NULL to use 
the account user */
+                                                const gchar *folder_id,
+                                                const gchar *message_id,
+                                                EO365ConnectionRawDataFunc func,
+                                                gpointer func_user_data,
+                                                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 4cd74854..7e86ea52 100644
--- a/src/Office365/common/e-o365-json-utils.c
+++ b/src/Office365/common/e-o365-json-utils.c
@@ -159,6 +159,29 @@ e_o365_json_get_string_member (JsonObject *object,
        return json_node_get_string (node);
 }
 
+time_t
+e_o365_get_date_time_offset_member (JsonObject *object,
+                                   const gchar *member_name)
+{
+       const gchar *value;
+       time_t res = (time_t) 0;
+
+       value = e_o365_json_get_string_member (object, member_name, NULL);
+
+       if (value) {
+               GDateTime *dt;
+
+               dt = g_date_time_new_from_iso8601 (value, NULL);
+
+               if (dt) {
+                       res = (time_t) g_date_time_to_unix (dt);
+                       g_date_time_unref (dt);
+               }
+       }
+
+       return res;
+}
+
 /* https://docs.microsoft.com/en-us/graph/delta-query-overview */
 
 gboolean
@@ -170,37 +193,376 @@ e_o365_delta_is_removed_object (JsonObject *object)
 /* https://docs.microsoft.com/en-us/graph/api/resources/mailfolder?view=graph-rest-1.0 */
 
 const gchar *
-e_o365_mail_folder_get_display_name (JsonObject *object)
+e_o365_mail_folder_get_display_name (EO365MailFolder *folder)
 {
-       return e_o365_json_get_string_member (object, "displayName", NULL);
+       return e_o365_json_get_string_member (folder, "displayName", NULL);
 }
 
 const gchar *
-e_o365_mail_folder_get_id (JsonObject *object)
+e_o365_mail_folder_get_id (EO365MailFolder *folder)
 {
-       return e_o365_json_get_string_member (object, "id", NULL);
+       return e_o365_json_get_string_member (folder, "id", NULL);
 }
 
 const gchar *
-e_o365_mail_folder_get_parent_folder_id (JsonObject *object)
+e_o365_mail_folder_get_parent_folder_id (EO365MailFolder *folder)
 {
-       return e_o365_json_get_string_member (object, "parentFolderId", NULL);
+       return e_o365_json_get_string_member (folder, "parentFolderId", NULL);
 }
 
 gint32
-e_o365_mail_folder_get_child_folder_count (JsonObject *object)
+e_o365_mail_folder_get_child_folder_count (EO365MailFolder *folder)
 {
-       return (gint32) e_o365_json_get_int_member (object, "childFolderCount", 0);
+       return (gint32) e_o365_json_get_int_member (folder, "childFolderCount", 0);
 }
 
 gint32
-e_o365_mail_folder_get_total_item_count (JsonObject *object)
+e_o365_mail_folder_get_total_item_count (EO365MailFolder *folder)
 {
-       return (gint32) e_o365_json_get_int_member (object, "totalItemCount", 0);
+       return (gint32) e_o365_json_get_int_member (folder, "totalItemCount", 0);
 }
 
 gint32
-e_o365_mail_folder_get_unread_item_count (JsonObject *object)
+e_o365_mail_folder_get_unread_item_count (EO365MailFolder *folder)
+{
+       return (gint32) e_o365_json_get_int_member (folder, "unreadItemCount", 0);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/recipient?view=graph-rest-1.0
+   https://docs.microsoft.com/en-us/graph/api/resources/emailaddress?view=graph-rest-1.0
+ */
+const gchar *
+e_o365_recipient_get_address (EO365Recipient *recipient)
+{
+       JsonObject *email_address;
+
+       email_address = e_o365_json_get_object_member (recipient, "emailAddress");
+
+       if (!email_address)
+               return NULL;
+
+       return e_o365_json_get_string_member (email_address, "address", NULL);
+}
+
+const gchar *
+e_o365_recipient_get_name (EO365Recipient *recipient)
+{
+       JsonObject *email_address;
+
+       email_address = e_o365_json_get_object_member (recipient, "emailAddress");
+
+       if (!email_address)
+               return NULL;
+
+       return e_o365_json_get_string_member (email_address, "name", NULL);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/datetimetimezone?view=graph-rest-1.0 */
+
+time_t
+e_o365_date_time_get_date_time (EO365DateTimeWithZone *datetime)
+{
+       return e_o365_get_date_time_offset_member (datetime, "dateTime");
+}
+
+const gchar *
+e_o365_date_time_get_time_zone (EO365DateTimeWithZone *datetime)
+{
+       return e_o365_json_get_string_member (datetime, "timeZone", NULL);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/internetmessageheader?view=graph-rest-1.0 */
+
+const gchar *
+e_o365_internet_message_header_get_name (EO365InternetMessageHeader *header)
+{
+       return e_o365_json_get_string_member (header, "name", NULL);
+}
+
+const gchar *
+e_o365_internet_message_header_get_value (EO365InternetMessageHeader *header)
+{
+       return e_o365_json_get_string_member (header, "value", NULL);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/followupflag?view=graph-rest-1.0 */
+
+EO365DateTimeWithZone *
+e_o365_followup_flag_get_completed_date_time (EO365FollowupFlag *flag)
+{
+       return e_o365_json_get_object_member (flag, "completedDateTime");
+}
+
+EO365DateTimeWithZone *
+e_o365_followup_flag_get_due_date_time (EO365FollowupFlag *flag)
+{
+       return e_o365_json_get_object_member (flag, "dueDateTime");
+}
+
+EO365FollowupFlagStatusType
+e_o365_followup_flag_get_flag_status (EO365FollowupFlag *flag)
+{
+       const gchar *status;
+
+       status = e_o365_json_get_string_member (flag, "flagStatus", NULL);
+
+       if (!status)
+               return E_O365_FOLLOWUP_FLAG_STATUS_NOT_SET;
+
+       if (g_strcmp0 (status, "notFlagged") == 0)
+               return E_O365_FOLLOWUP_FLAG_STATUS_NOT_FLAGGED;
+
+       if (g_strcmp0 (status, "complete") == 0)
+               return E_O365_FOLLOWUP_FLAG_STATUS_COMPLETE;
+
+       if (g_strcmp0 (status, "flagged") == 0)
+               return E_O365_FOLLOWUP_FLAG_STATUS_FLAGGED;
+
+       return E_O365_FOLLOWUP_FLAG_STATUS_UNKNOWN;
+}
+
+EO365DateTimeWithZone *
+e_o365_followup_flag_get_start_date_time (EO365FollowupFlag *flag)
+{
+       return e_o365_json_get_object_member (flag, "startDateTime");
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/itembody?view=graph-rest-1.0 */
+
+const gchar *
+e_o365_item_body_get_content (EO365ItemBody *item_body)
+{
+       return e_o365_json_get_string_member (item_body, "content", NULL);
+}
+
+EO365ItemBodyContentTypeType
+e_o365_item_body_get_content_type (EO365ItemBody *item_body)
+{
+       const gchar *content_type;
+
+       content_type = e_o365_json_get_string_member (item_body, "contentType", NULL);
+
+       if (!content_type)
+               return E_O365_ITEM_BODY_CONTENT_TYPE_NOT_SET;
+
+       if (g_strcmp0 (content_type, "text") == 0)
+               return E_O365_ITEM_BODY_CONTENT_TYPE_TEXT;
+
+       if (g_strcmp0 (content_type, "html") == 0)
+               return E_O365_ITEM_BODY_CONTENT_TYPE_HTML;
+
+       return E_O365_ITEM_BODY_CONTENT_TYPE_UNKNOWN;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0 */
+
+JsonArray * /* EO365Recipient * */
+e_o365_mail_message_get_bcc_recipients (EO365MailMessage *mail)
+{
+       return e_o365_json_get_array_member (mail, "bccRecipients");
+}
+
+EO365ItemBody *
+e_o365_mail_message_get_body (EO365MailMessage *mail)
+{
+       return e_o365_json_get_object_member (mail, "body");
+}
+
+const gchar *
+e_o365_mail_message_get_body_preview (EO365MailMessage *mail)
+{
+       return e_o365_json_get_string_member (mail, "bodyPreview", NULL);
+}
+
+JsonArray * /* const gchar * */
+e_o365_mail_message_get_categories (EO365MailMessage *mail)
+{
+       return e_o365_json_get_array_member (mail, "categories");
+}
+
+JsonArray * /* EO365Recipient * */
+e_o365_mail_message_get_cc_recipients (EO365MailMessage *mail)
+{
+       return e_o365_json_get_array_member (mail, "ccRecipients");
+}
+
+const gchar *
+e_o365_mail_message_get_change_key (EO365MailMessage *mail)
+{
+       return e_o365_json_get_string_member (mail, "changeKey", NULL);
+}
+
+const gchar *
+e_o365_mail_message_get_conversation_id (EO365MailMessage *mail)
+{
+       return e_o365_json_get_string_member (mail, "conversationId", NULL);
+}
+
+JsonObject * /* Edm.Binary */
+e_o365_mail_message_get_conversation_index (EO365MailMessage *mail)
+{
+       return e_o365_json_get_object_member (mail, "conversationIndex");
+}
+
+time_t
+e_o365_mail_message_get_created_date_time (EO365MailMessage *mail)
+{
+       return e_o365_get_date_time_offset_member (mail, "createdDateTime");
+}
+
+EO365FollowupFlag *
+e_o365_mail_message_get_flag (EO365MailMessage *mail)
+{
+       return e_o365_json_get_object_member (mail, "flag");
+}
+
+EO365Recipient *
+e_o365_mail_message_get_from (EO365MailMessage *mail)
+{
+       return e_o365_json_get_object_member (mail, "from");
+}
+
+gboolean
+e_o365_mail_message_get_has_attachments        (EO365MailMessage *mail)
+{
+       return e_o365_json_get_boolean_member (mail, "hasAttachments", FALSE);
+}
+
+const gchar *
+e_o365_mail_message_get_id (EO365MailMessage *mail)
+{
+       return e_o365_json_get_string_member (mail, "id", NULL);
+}
+
+EO365ImportanceType
+e_o365_mail_message_get_importance (EO365MailMessage *mail)
+{
+       const gchar *value = e_o365_json_get_string_member (mail, "importance", NULL);
+
+       if (!value)
+               return E_O365_IMPORTANCE_NOT_SET;
+
+       if (g_strcmp0 (value, "Low") == 0)
+               return E_O365_IMPORTANCE_LOW;
+
+       if (g_strcmp0 (value, "Normal") == 0)
+               return E_O365_IMPORTANCE_NORMAL;
+
+       if (g_strcmp0 (value, "High") == 0)
+               return E_O365_IMPORTANCE_HIGH;
+
+       return E_O365_IMPORTANCE_UNKNOWN;
+}
+
+EO365InferenceClassificationType
+e_o365_mail_message_get_inference_classification (EO365MailMessage *mail)
+{
+       const gchar *value = e_o365_json_get_string_member (mail, "inferenceClassification", NULL);
+
+       if (!value)
+               return E_O365_INFERENCE_CLASSIFICATION_NOT_SET;
+
+       if (g_strcmp0 (value, "focused") == 0)
+               return E_O365_INFERENCE_CLASSIFICATION_FOCUSED;
+
+       if (g_strcmp0 (value, "other") == 0)
+               return E_O365_INFERENCE_CLASSIFICATION_OTHER;
+
+       return E_O365_INFERENCE_CLASSIFICATION_UNKNOWN;
+}
+
+JsonArray * /* EO365InternetMessageHeader * */
+e_o365_mail_message_get_internet_message_headers (EO365MailMessage *mail)
+{
+       return e_o365_json_get_array_member (mail, "internetMessageHeaders");
+}
+
+const gchar *
+e_o365_mail_message_get_internet_message_id (EO365MailMessage *mail)
+{
+       return e_o365_json_get_string_member (mail, "internetMessageId", NULL);
+}
+
+gboolean
+e_o365_mail_message_get_is_delivery_receipt_requested (EO365MailMessage *mail)
+{
+       return e_o365_json_get_boolean_member (mail, "isDeliveryReceiptRequested", FALSE);
+}
+
+gboolean
+e_o365_mail_message_get_is_draft (EO365MailMessage *mail)
+{
+       return e_o365_json_get_boolean_member (mail, "isDraft", FALSE);
+}
+
+gboolean
+e_o365_mail_message_get_is_read (EO365MailMessage *mail)
+{
+       return e_o365_json_get_boolean_member (mail, "isRead", FALSE);
+}
+
+gboolean
+e_o365_mail_message_get_is_read_receipt_requested (EO365MailMessage *mail)
+{
+       return e_o365_json_get_boolean_member (mail, "isReadReceiptRequested", FALSE);
+}
+
+time_t
+e_o365_mail_message_get_last_modified_date_time (EO365MailMessage *mail)
+{
+       return e_o365_get_date_time_offset_member (mail, "lastModifiedDateTime");
+}
+
+const gchar *
+e_o365_mail_message_get_parent_folder_id (EO365MailMessage *mail)
+{
+               return e_o365_json_get_string_member (mail, "parentFolderId", NULL);
+}
+
+time_t
+e_o365_mail_message_get_received_date_time (EO365MailMessage *mail)
+{
+       return e_o365_get_date_time_offset_member (mail, "receivedDateTime");
+}
+
+JsonArray * /* EO365Recipient * */
+e_o365_mail_message_get_reply_to (EO365MailMessage *mail)
+{
+       return e_o365_json_get_array_member (mail, "replyTo");
+}
+
+EO365Recipient *
+e_o365_mail_message_get_sender (EO365MailMessage *mail)
+{
+       return e_o365_json_get_object_member (mail, "sender");
+}
+
+time_t
+e_o365_mail_message_get_sent_date_time (EO365MailMessage *mail)
+{
+       return e_o365_get_date_time_offset_member (mail, "sentDateTime");
+}
+
+const gchar *
+e_o365_mail_message_get_subject (EO365MailMessage *mail)
+{
+       return e_o365_json_get_string_member (mail, "subject", NULL);
+}
+
+JsonArray * /* EO365Recipient * */
+e_o365_mail_message_get_to_recipients (EO365MailMessage *mail)
+{
+       return e_o365_json_get_array_member (mail, "toRecipients");
+}
+
+EO365ItemBody *
+e_o365_mail_message_get_unique_body (EO365MailMessage *mail)
+{
+       return e_o365_json_get_object_member (mail, "uniqueBody");
+}
+
+const gchar *
+e_o365_mail_message_get_web_link (EO365MailMessage *mail)
 {
-       return (gint32) e_o365_json_get_int_member (object, "unreadItemCount", 0);
+       return e_o365_json_get_string_member (mail, "webLink", NULL);
 }
diff --git a/src/Office365/common/e-o365-json-utils.h b/src/Office365/common/e-o365-json-utils.h
index f1ed0300..12aeecc4 100644
--- a/src/Office365/common/e-o365-json-utils.h
+++ b/src/Office365/common/e-o365-json-utils.h
@@ -18,10 +18,50 @@
 #ifndef E_O365_JSON_UTILS_H
 #define E_O365_JSON_UTILS_H
 
+#include <time.h>
 #include <json-glib/json-glib.h>
 
 G_BEGIN_DECLS
 
+typedef enum _EO365InferenceClassificationType {
+       E_O365_INFERENCE_CLASSIFICATION_NOT_SET,
+       E_O365_INFERENCE_CLASSIFICATION_UNKNOWN,
+       E_O365_INFERENCE_CLASSIFICATION_FOCUSED,
+       E_O365_INFERENCE_CLASSIFICATION_OTHER
+} EO365InferenceClassificationType;
+
+typedef enum _EO365ImportanceType {
+       E_O365_IMPORTANCE_NOT_SET,
+       E_O365_IMPORTANCE_UNKNOWN,
+       E_O365_IMPORTANCE_LOW,
+       E_O365_IMPORTANCE_NORMAL,
+       E_O365_IMPORTANCE_HIGH
+} EO365ImportanceType;
+
+typedef enum _EO365FollowupFlagStatusType {
+       E_O365_FOLLOWUP_FLAG_STATUS_NOT_SET,
+       E_O365_FOLLOWUP_FLAG_STATUS_UNKNOWN,
+       E_O365_FOLLOWUP_FLAG_STATUS_NOT_FLAGGED,
+       E_O365_FOLLOWUP_FLAG_STATUS_COMPLETE,
+       E_O365_FOLLOWUP_FLAG_STATUS_FLAGGED
+} EO365FollowupFlagStatusType;
+
+typedef enum _EO365ItemBodyContentTypeType {
+       E_O365_ITEM_BODY_CONTENT_TYPE_NOT_SET,
+       E_O365_ITEM_BODY_CONTENT_TYPE_UNKNOWN,
+       E_O365_ITEM_BODY_CONTENT_TYPE_TEXT,
+       E_O365_ITEM_BODY_CONTENT_TYPE_HTML
+} EO365ItemBodyContentTypeType;
+
+/* Just for better readability */
+#define EO365MailFolder JsonObject
+#define EO365Recipient JsonObject
+#define EO365DateTimeWithZone JsonObject
+#define EO365FollowupFlag JsonObject
+#define EO365InternetMessageHeader JsonObject
+#define EO365ItemBody JsonObject
+#define EO365MailMessage JsonObject
+
 JsonArray *    e_o365_json_get_array_member            (JsonObject *object,
                                                         const gchar *member_name);
 gboolean       e_o365_json_get_boolean_member          (JsonObject *object,
@@ -42,15 +82,86 @@ const gchar *       e_o365_json_get_string_member           (JsonObject *object,
                                                         const gchar *member_name,
                                                         const gchar *default_value);
 
+time_t         e_o365_get_date_time_offset_member      (JsonObject *object,
+                                                        const gchar *member_name);
+
 gboolean       e_o365_delta_is_removed_object          (JsonObject *object);
 
-const gchar *  e_o365_mail_folder_get_display_name     (JsonObject *object);
-const gchar *  e_o365_mail_folder_get_id               (JsonObject *object);
-const gchar *  e_o365_mail_folder_get_parent_folder_id (JsonObject *object);
+const gchar *  e_o365_mail_folder_get_display_name     (EO365MailFolder *folder);
+const gchar *  e_o365_mail_folder_get_id               (EO365MailFolder *folder);
+const gchar *  e_o365_mail_folder_get_parent_folder_id (EO365MailFolder *folder);
 gint32         e_o365_mail_folder_get_child_folder_count
-                                                       (JsonObject *object);
-gint32         e_o365_mail_folder_get_total_item_count (JsonObject *object);
-gint32         e_o365_mail_folder_get_unread_item_count(JsonObject *object);
+                                                       (EO365MailFolder *folder);
+gint32         e_o365_mail_folder_get_total_item_count (EO365MailFolder *folder);
+gint32         e_o365_mail_folder_get_unread_item_count(EO365MailFolder *folder);
+
+const gchar *  e_o365_recipient_get_address            (EO365Recipient *recipient);
+const gchar *  e_o365_recipient_get_name               (EO365Recipient *recipient);
+
+time_t         e_o365_date_time_get_date_time          (EO365DateTimeWithZone *datetime);
+const gchar *  e_o365_date_time_get_time_zone          (EO365DateTimeWithZone *datetime);
+
+const gchar *  e_o365_internet_message_header_get_name (EO365InternetMessageHeader *header);
+const gchar *  e_o365_internet_message_header_get_value(EO365InternetMessageHeader *header);
+
+EO365DateTimeWithZone *
+               e_o365_followup_flag_get_completed_date_time
+                                                       (EO365FollowupFlag *flag);
+EO365DateTimeWithZone *
+               e_o365_followup_flag_get_due_date_time  (EO365FollowupFlag *flag);
+EO365FollowupFlagStatusType
+               e_o365_followup_flag_get_flag_status    (EO365FollowupFlag *flag);
+EO365DateTimeWithZone *
+               e_o365_followup_flag_get_start_date_time(EO365FollowupFlag *flag);
+
+const gchar *  e_o365_item_body_get_content            (EO365ItemBody *item_body);
+EO365ItemBodyContentTypeType
+               e_o365_item_body_get_content_type       (EO365ItemBody *item_body);
+
+JsonArray *    e_o365_mail_message_get_bcc_recipients  (EO365MailMessage *mail); /* EO365Recipient * */
+EO365ItemBody *        e_o365_mail_message_get_body            (EO365MailMessage *mail);
+const gchar *  e_o365_mail_message_get_body_preview    (EO365MailMessage *mail);
+JsonArray *    e_o365_mail_message_get_categories      (EO365MailMessage *mail); /* const gchar * */
+JsonArray *    e_o365_mail_message_get_cc_recipients   (EO365MailMessage *mail); /* EO365Recipient * */
+const gchar *  e_o365_mail_message_get_change_key      (EO365MailMessage *mail);
+const gchar *  e_o365_mail_message_get_conversation_id (EO365MailMessage *mail);
+JsonObject *   e_o365_mail_message_get_conversation_index
+                                                       (EO365MailMessage *mail);
+time_t         e_o365_mail_message_get_created_date_time
+                                                       (EO365MailMessage *mail);
+EO365FollowupFlag *
+               e_o365_mail_message_get_flag            (EO365MailMessage *mail);
+EO365Recipient *
+               e_o365_mail_message_get_from            (EO365MailMessage *mail);
+gboolean       e_o365_mail_message_get_has_attachments (EO365MailMessage *mail);
+const gchar *  e_o365_mail_message_get_id              (EO365MailMessage *mail);
+EO365ImportanceType
+               e_o365_mail_message_get_importance      (EO365MailMessage *mail);
+EO365InferenceClassificationType
+               e_o365_mail_message_get_inference_classification
+                                                       (EO365MailMessage *mail);
+JsonArray *    e_o365_mail_message_get_internet_message_headers
+                                                       (EO365MailMessage *mail); /* 
EO365InternetMessageHeader * */
+const gchar *  e_o365_mail_message_get_internet_message_id
+                                                       (EO365MailMessage *mail);
+gboolean       e_o365_mail_message_get_is_delivery_receipt_requested
+                                                       (EO365MailMessage *mail);
+gboolean       e_o365_mail_message_get_is_draft        (EO365MailMessage *mail);
+gboolean       e_o365_mail_message_get_is_read         (EO365MailMessage *mail);
+gboolean       e_o365_mail_message_get_is_read_receipt_requested
+                                                       (EO365MailMessage *mail);
+time_t         e_o365_mail_message_get_last_modified_date_time
+                                                       (EO365MailMessage *mail);
+const gchar *  e_o365_mail_message_get_parent_folder_id(EO365MailMessage *mail);
+time_t         e_o365_mail_message_get_received_date_time
+                                                       (EO365MailMessage *mail);
+JsonArray *    e_o365_mail_message_get_reply_to        (EO365MailMessage *mail); /* EO365Recipient * */
+EO365Recipient *e_o365_mail_message_get_sender         (EO365MailMessage *mail);
+time_t         e_o365_mail_message_get_sent_date_time  (EO365MailMessage *mail);
+const gchar *  e_o365_mail_message_get_subject         (EO365MailMessage *mail);
+JsonArray *    e_o365_mail_message_get_to_recipients   (EO365MailMessage *mail); /* EO365Recipient * */
+EO365ItemBody *        e_o365_mail_message_get_unique_body     (EO365MailMessage *mail);
+const gchar *  e_o365_mail_message_get_web_link        (EO365MailMessage *mail);
 
 G_END_DECLS
 
diff --git a/src/Office365/evolution/e-mail-config-o365-backend.c 
b/src/Office365/evolution/e-mail-config-o365-backend.c
index 73781265..e0e76b6d 100644
--- a/src/Office365/evolution/e-mail-config-o365-backend.c
+++ b/src/Office365/evolution/e-mail-config-o365-backend.c
@@ -27,7 +27,6 @@
 
 #include "common/camel-o365-settings.h"
 #include "common/e-o365-connection.h"
-#include "common/e-o365-json-utils.h"
 
 #include "e-mail-config-o365-backend.h"
 
@@ -103,7 +102,7 @@ test_clicked_cb (GtkButton *button,
        cnc = e_o365_connection_new (source, CAMEL_O365_SETTINGS (settings));
        g_return_if_fail (cnc != NULL);
 
-       //success = e_o365_connection_list_folders_sync (cnc, NULL, NULL, NULL, &folders, NULL, &error);
+       //success = e_o365_connection_list_mail_folders_sync (cnc, NULL, NULL, NULL, &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) {


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