[evolution-data-server/wip/camel-more-gobject] Initial fill-up of CamelMessageInfo and CamelMessageInfoBase



commit f60d15301b735e0f66f375ad6add58e39c401e42
Author: Milan Crha <mcrha redhat com>
Date:   Fri Sep 2 15:38:28 2016 +0200

    Initial fill-up of CamelMessageInfo and CamelMessageInfoBase

 camel/Makefile.am                  |    4 +-
 camel/camel-folder-summary.c       |    4 +
 camel/camel-folder-summary.h       |   44 +-
 camel/camel-message-content-info.c |   16 -
 camel/camel-message-content-info.h |   16 -
 camel/camel-message-info-base.c    |  865 ++++++++++++++
 camel/camel-message-info-base.h    |   67 ++
 camel/camel-message-info.c         | 2267 +++++++++++++++++++++++++++++++++++-
 camel/camel-message-info.h         |  222 ++++-
 camel/camel-mime-utils.c           |  822 +++++++++++++
 camel/camel-mime-utils.h           |   92 ++-
 camel/camel.h                      |    2 +-
 12 files changed, 4324 insertions(+), 97 deletions(-)
---
diff --git a/camel/Makefile.am b/camel/Makefile.am
index 7573bfa..646fde3 100644
--- a/camel/Makefile.am
+++ b/camel/Makefile.am
@@ -93,7 +93,7 @@ libcamel_1_2_la_SOURCES = \
        camel-memchunk.c \
        camel-mempool.c \
        camel-message-info.c \
-       camel-message-content-info.c \
+       camel-message-info-base.c \
        camel-mime-filter-basic.c \
        camel-mime-filter-bestenc.c \
        camel-mime-filter-canon.c \
@@ -213,7 +213,7 @@ libcamelinclude_HEADERS = \
        camel-memchunk.h \
        camel-mempool.h \
        camel-message-info.h \
-       camel-message-content-info.h \
+       camel-message-info-base.h \
        camel-mime-filter-basic.h \
        camel-mime-filter-bestenc.h \
        camel-mime-filter-canon.h \
diff --git a/camel/camel-folder-summary.c b/camel/camel-folder-summary.c
index eb46cfe..ecc0451 100644
--- a/camel/camel-folder-summary.c
+++ b/camel/camel-folder-summary.c
@@ -39,6 +39,8 @@
 #include "camel-folder-summary.h"
 #include "camel-folder.h"
 #include "camel-iconv.h"
+#include "camel-message-info.h"
+#include "camel-message-info-base.h"
 #include "camel-mime-filter-basic.h"
 #include "camel-mime-filter-charset.h"
 #include "camel-mime-filter-html.h"
@@ -1155,6 +1157,8 @@ camel_folder_summary_class_init (CamelFolderSummaryClass *class)
        object_class->dispose = folder_summary_dispose;
        object_class->finalize = folder_summary_finalize;
 
+       class->message_info_type = XCAMEL_TYPE_MESSAGE_INFO_BASE;
+
        class->message_info_size = sizeof (CamelMessageInfoBase);
        class->content_info_size = sizeof (CamelMessageContentInfo);
 
diff --git a/camel/camel-folder-summary.h b/camel/camel-folder-summary.h
index 192d527..7462190 100644
--- a/camel/camel-folder-summary.h
+++ b/camel/camel-folder-summary.h
@@ -80,36 +80,6 @@ struct _CamelMessageContentInfo {
        guint32 size;
 };
 
-/* system flag bits */
-typedef enum _CamelMessageFlags {
-       CAMEL_MESSAGE_ANSWERED = 1 << 0,
-       CAMEL_MESSAGE_DELETED = 1 << 1,
-       CAMEL_MESSAGE_DRAFT = 1 << 2,
-       CAMEL_MESSAGE_FLAGGED = 1 << 3,
-       CAMEL_MESSAGE_SEEN = 1 << 4,
-
-       /* these aren't really system flag bits, but are convenience flags */
-       CAMEL_MESSAGE_ATTACHMENTS = 1 << 5,
-       CAMEL_MESSAGE_ANSWERED_ALL = 1 << 6,
-       CAMEL_MESSAGE_JUNK = 1 << 7,
-       CAMEL_MESSAGE_SECURE = 1 << 8,
-       CAMEL_MESSAGE_NOTJUNK = 1 << 9,
-       CAMEL_MESSAGE_FORWARDED = 1 << 10,
-
-       /* following flags are for the folder, and are not really permanent flags */
-       CAMEL_MESSAGE_FOLDER_FLAGGED = 1 << 16, /* for use by the folder implementation */
-       /* flags after 1 << 16 are used by camel providers,
- *         if adding non permanent flags, add them to the end  */
-
-       CAMEL_MESSAGE_JUNK_LEARN = 1 << 30, /* used when setting CAMEL_MESSAGE_JUNK flag
-                                            * to say that we request junk plugin
-                                            * to learn that message as junk/non junk */
-       CAMEL_MESSAGE_USER = 1 << 31 /* supports user flags */
-} CamelMessageFlags;
-
-/* Changes to system flags will NOT trigger a folder changed event */
-#define CAMEL_MESSAGE_SYSTEM_MASK (0xffff << 16)
-
 typedef struct _CamelFlag {
        struct _CamelFlag *next;
        gchar name[1];          /* name allocated as part of the structure */
@@ -121,18 +91,6 @@ typedef struct _CamelTag {
        gchar name[1];          /* name allocated as part of the structure */
 } CamelTag;
 
-/* a summary messageid is a 64 bit identifier (partial md5 hash) */
-typedef struct _CamelSummaryMessageID {
-       union {
-               guint64 id;
-               guchar hash[8];
-               struct {
-                       guint32 hi;
-                       guint32 lo;
-               } part;
-       } id;
-} CamelSummaryMessageID;
-
 /* summary references is a fixed size array of references */
 typedef struct _CamelSummaryReferences {
        gint size;
@@ -245,6 +203,8 @@ struct _CamelFIRecord;
 struct _CamelFolderSummaryClass {
        GObjectClass parent_class;
 
+       GType message_info_type;
+
        /* sizes of memory objects */
        gsize message_info_size;
        gsize content_info_size;
diff --git a/camel/camel-message-info-base.c b/camel/camel-message-info-base.c
new file mode 100644
index 0000000..ca361de
--- /dev/null
+++ b/camel/camel-message-info-base.c
@@ -0,0 +1,865 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+
+#include "camel-folder-summary.h"
+#include "camel-message-info.h"
+
+#include "camel-message-info-base.h"
+
+struct _xCamelMessageInfoBasePrivate {
+       guint32 flags;          /* bit-or of CamelMessageFlags */
+       CamelNamedFlags *user_flags;
+       CamelNameValueArray *user_tags;
+       gchar *subject;
+       gchar *preview;
+       gchar *from;
+       gchar *to;
+       gchar *cc;
+       gchar *mlist;
+       guint32 size;
+       gint64 date_sent;       /* aka time_t */
+       gint64 date_received;   /* aka time_t */
+       guint64 message_id;
+       GArray *references;     /* guint64, aka CamelSummaryMessageID */
+       CamelNameValueArray *headers;
+};
+
+G_DEFINE_TYPE (xCamelMessageInfoBase, xcamel_message_info_base, XCAMEL_TYPE_MESSAGE_INFO)
+
+static guint32
+message_info_base_get_flags (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       guint32 result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), 0);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->flags;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_flags (xCamelMessageInfo *mi,
+                            CamelMessageFlags mask,
+                            guint32 set)
+{
+       xCamelMessageInfoBase *bmi;
+       guint32 old_flags;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       old_flags = bmi->priv->flags;
+       bmi->priv->flags = (old_flags & ~mask) | (set & mask);
+       changed = old_flags != bmi->priv->flags;
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static gboolean
+message_info_base_get_user_flag (const xCamelMessageInfo *mi,
+                                const gchar *name)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = camel_named_flags_contains (bmi->priv->user_flags, name);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_user_flag (xCamelMessageInfo *mi,
+                                const gchar *name,
+                                gboolean state)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       if (!bmi->priv->user_flags)
+               bmi->priv->user_flags = camel_named_flags_new ();
+
+       if (state)
+               changed = camel_named_flags_insert (bmi->priv->user_flags, name);
+       else
+               changed = camel_named_flags_remove (bmi->priv->user_flags, name);
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static CamelNamedFlags *
+message_info_base_dup_user_flags (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       CamelNamedFlags *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), NULL);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       if (bmi->priv->user_flags)
+               result = camel_named_flags_copy (bmi->priv->user_flags);
+       else
+               result = NULL;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_take_user_flags (xCamelMessageInfo *mi,
+                                  CamelNamedFlags *user_flags)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       /* Consider them the same if they are the same pointer or they both are empty;
+          otherwise report change. */
+       changed = !(bmi->priv->user_flags == user_flags ||
+                  (bmi->priv->user_flags && user_flags &&
+                  !camel_named_flags_length (bmi->priv->user_flags) &&
+                  !camel_named_flags_length (user_flags)));
+
+       if (changed) {
+               camel_named_flags_free (bmi->priv->user_flags);
+               bmi->priv->user_flags = user_flags;
+       }
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static const gchar *
+message_info_base_get_user_tag (const xCamelMessageInfo *mi,
+                               const gchar *name)
+{
+       xCamelMessageInfoBase *bmi;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), NULL);
+       g_return_val_if_fail (name != NULL, NULL);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = camel_name_value_array_get_named (bmi->priv->user_tags, TRUE, name);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_user_tag (xCamelMessageInfo *mi,
+                               const gchar *name,
+                               const gchar *value)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       if (value)
+               changed = camel_name_value_array_set_named (bmi->priv->user_tags, TRUE, name, value);
+       else
+               changed = camel_name_value_array_remove_named (bmi->priv->user_tags, TRUE, name, FALSE);
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static CamelNameValueArray *
+message_info_base_dup_user_tags (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       CamelNameValueArray *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), NULL);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = camel_name_value_array_copy (bmi->priv->user_tags);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_take_user_tags (xCamelMessageInfo *mi,
+                                 CamelNameValueArray *user_tags)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       /* Consider them the same if they are the same pointer or they both are empty;
+          otherwise report change. */
+       changed = !(bmi->priv->user_tags == user_tags ||
+                  (bmi->priv->user_tags && user_tags &&
+                  !camel_name_value_array_length (bmi->priv->user_tags) &&
+                  !camel_name_value_array_length (user_tags)));
+
+       if (changed) {
+               camel_name_value_array_free (bmi->priv->user_tags);
+               bmi->priv->user_tags = user_tags;
+       }
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static const gchar *
+message_info_base_get_subject (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), NULL);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->subject;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_subject (xCamelMessageInfo *mi,
+                              const gchar *subject)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       changed = g_strcmp0 (bmi->priv->subject, subject) != 0;
+
+       if (changed) {
+               g_free (bmi->priv->subject);
+               bmi->priv->subject = g_strdup (subject);
+       }
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static const gchar *
+message_info_base_get_preview (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), NULL);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->preview;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_preview (xCamelMessageInfo *mi,
+                              const gchar *preview)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       changed = g_strcmp0 (bmi->priv->preview, preview) != 0;
+
+       if (changed) {
+               g_free (bmi->priv->preview);
+               bmi->priv->preview = g_strdup (preview);
+       }
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static const gchar *
+message_info_base_get_from (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), NULL);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->from;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_from (xCamelMessageInfo *mi,
+                           const gchar *from)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       changed = g_strcmp0 (bmi->priv->from, from) != 0;
+
+       if (changed) {
+               g_free (bmi->priv->from);
+               bmi->priv->from = g_strdup (from);
+       }
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static const gchar *
+message_info_base_get_to (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), NULL);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->to;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_to (xCamelMessageInfo *mi,
+                         const gchar *to)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       changed = g_strcmp0 (bmi->priv->to, to) != 0;
+
+       if (changed) {
+               g_free (bmi->priv->to);
+               bmi->priv->to = g_strdup (to);
+       }
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static const gchar *
+message_info_base_get_cc (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), NULL);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->cc;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_cc (xCamelMessageInfo *mi,
+                         const gchar *cc)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       changed = g_strcmp0 (bmi->priv->cc, cc) != 0;
+
+       if (changed) {
+               g_free (bmi->priv->cc);
+               bmi->priv->cc = g_strdup (cc);
+       }
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static const gchar *
+message_info_base_get_mlist (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), NULL);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->mlist;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_mlist (xCamelMessageInfo *mi,
+                            const gchar *mlist)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       changed = g_strcmp0 (bmi->priv->mlist, mlist) != 0;
+
+       if (changed) {
+               g_free (bmi->priv->mlist);
+               bmi->priv->mlist = g_strdup (mlist);
+       }
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static guint32
+message_info_base_get_size (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       guint32 result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), 0);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->size;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_size (xCamelMessageInfo *mi,
+                           guint32 size)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       changed = bmi->priv->size != size;
+
+       if (changed)
+               bmi->priv->size = size;
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static gint64
+message_info_base_get_date_sent (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       gint64 result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), 0);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->date_sent;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_date_sent (xCamelMessageInfo *mi,
+                                gint64 date_sent)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       changed = bmi->priv->date_sent != date_sent;
+
+       if (changed)
+               bmi->priv->date_sent = date_sent;
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static gint64
+message_info_base_get_date_received (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       gint64 result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), 0);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->date_received;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_date_received (xCamelMessageInfo *mi,
+                                    gint64 date_received)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       changed = bmi->priv->date_received != date_received;
+
+       if (changed)
+               bmi->priv->date_received = date_received;
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static guint64
+message_info_base_get_message_id (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       guint64 result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), 0);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->message_id;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_set_message_id (xCamelMessageInfo *mi,
+                                 guint64 message_id)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       changed = bmi->priv->message_id != message_id;
+
+       if (changed)
+               bmi->priv->message_id = message_id;
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static const GArray *
+message_info_base_get_references (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       const GArray *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), NULL);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->references;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_take_references (xCamelMessageInfo *mi,
+                                  GArray *references)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       /* Consider them the same if they are the same pointer or they both are empty;
+          otherwise report change. */
+       changed = !(bmi->priv->references == references ||
+                  (bmi->priv->references && references &&
+                  !bmi->priv->references->len &&
+                  !references->len));
+
+       if (changed) {
+               if (bmi->priv->references)
+                       g_array_unref (bmi->priv->references);
+               bmi->priv->references = references;
+       }
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static const CamelNameValueArray *
+message_info_base_get_headers (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoBase *bmi;
+       const CamelNameValueArray *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), NULL);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+       result = bmi->priv->headers;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+static gboolean
+message_info_base_take_headers (xCamelMessageInfo *mi,
+                               CamelNameValueArray *headers)
+{
+       xCamelMessageInfoBase *bmi;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO_BASE (mi), FALSE);
+
+       bmi = XCAMEL_MESSAGE_INFO_BASE (mi);
+
+       xcamel_message_info_property_lock (mi);
+
+       /* Consider them the same if they are the same pointer or they both are empty;
+          otherwise report change. */
+       changed = !(bmi->priv->headers == headers ||
+                  (bmi->priv->headers && headers &&
+                  !camel_name_value_array_length (bmi->priv->headers) &&
+                  !camel_name_value_array_length (headers)));
+
+       if (changed) {
+               if (bmi->priv->headers)
+                       camel_name_value_array_free (bmi->priv->headers);
+               bmi->priv->headers = headers;
+       }
+
+       xcamel_message_info_property_unlock (mi);
+
+       return changed;
+}
+
+static void
+message_info_base_dispose (GObject *object)
+{
+       xCamelMessageInfoBase *bmi = XCAMEL_MESSAGE_INFO_BASE (object);
+
+       camel_name_value_array_free (bmi->priv->user_tags);
+       bmi->priv->user_tags = NULL;
+
+       camel_named_flags_free (bmi->priv->user_flags);
+       bmi->priv->user_flags = NULL;
+
+       #define free_ptr(x) G_STMT_START { g_free (x); x = NULL; } G_STMT_END
+
+       free_ptr (bmi->priv->subject);
+       free_ptr (bmi->priv->preview);
+       free_ptr (bmi->priv->from);
+       free_ptr (bmi->priv->to);
+       free_ptr (bmi->priv->cc);
+       free_ptr (bmi->priv->mlist);
+
+       #undef free_ptr
+
+       if (bmi->priv->references) {
+               g_array_unref (bmi->priv->references);
+               bmi->priv->references = NULL;
+       }
+
+       camel_name_value_array_free (bmi->priv->headers);
+       bmi->priv->headers = NULL;
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (xcamel_message_info_base_parent_class)->dispose (object);
+}
+
+static void
+xcamel_message_info_base_class_init (xCamelMessageInfoBaseClass *class)
+{
+       xCamelMessageInfoClass *mi_class;
+       GObjectClass *object_class;
+
+       g_type_class_add_private (class, sizeof (xCamelMessageInfoBasePrivate));
+
+       mi_class = XCAMEL_MESSAGE_INFO_CLASS (class);
+       mi_class->get_flags = message_info_base_get_flags;
+       mi_class->set_flags = message_info_base_set_flags;
+       mi_class->get_user_flag = message_info_base_get_user_flag;
+       mi_class->set_user_flag = message_info_base_set_user_flag;
+       mi_class->dup_user_flags = message_info_base_dup_user_flags;
+       mi_class->take_user_flags = message_info_base_take_user_flags;
+       mi_class->get_user_tag = message_info_base_get_user_tag;
+       mi_class->set_user_tag = message_info_base_set_user_tag;
+       mi_class->dup_user_tags = message_info_base_dup_user_tags;
+       mi_class->take_user_tags = message_info_base_take_user_tags;
+       mi_class->get_subject = message_info_base_get_subject;
+       mi_class->set_subject = message_info_base_set_subject;
+       mi_class->get_preview = message_info_base_get_preview;
+       mi_class->set_preview = message_info_base_set_preview;
+       mi_class->get_from = message_info_base_get_from;
+       mi_class->set_from = message_info_base_set_from;
+       mi_class->get_to = message_info_base_get_to;
+       mi_class->set_to = message_info_base_set_to;
+       mi_class->get_cc = message_info_base_get_cc;
+       mi_class->set_cc = message_info_base_set_cc;
+       mi_class->get_mlist = message_info_base_get_mlist;
+       mi_class->set_mlist = message_info_base_set_mlist;
+       mi_class->get_size = message_info_base_get_size;
+       mi_class->set_size = message_info_base_set_size;
+       mi_class->get_date_sent = message_info_base_get_date_sent;
+       mi_class->set_date_sent = message_info_base_set_date_sent;
+       mi_class->get_date_received = message_info_base_get_date_received;
+       mi_class->set_date_received = message_info_base_set_date_received;
+       mi_class->get_message_id = message_info_base_get_message_id;
+       mi_class->set_message_id = message_info_base_set_message_id;
+       mi_class->get_references = message_info_base_get_references;
+       mi_class->take_references = message_info_base_take_references;
+       mi_class->get_headers = message_info_base_get_headers;
+       mi_class->take_headers = message_info_base_take_headers;
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->dispose = message_info_base_dispose;
+}
+
+static void
+xcamel_message_info_base_init (xCamelMessageInfoBase *bmi)
+{
+       bmi->priv = G_TYPE_INSTANCE_GET_PRIVATE (bmi, XCAMEL_TYPE_MESSAGE_INFO_BASE, 
xCamelMessageInfoBasePrivate);
+}
diff --git a/camel/camel-message-info-base.h b/camel/camel-message-info-base.h
new file mode 100644
index 0000000..dc0a975
--- /dev/null
+++ b/camel/camel-message-info-base.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION)
+#error "Only <camel/camel.h> can be included directly."
+#endif
+
+#ifndef XCAMEL_MESSAGE_INFO_BASE_H
+#define XCAMEL_MESSAGE_INFO_BASE_H
+
+#include <glib-object.h>
+
+#include <camel/camel-message-info.h>
+
+/* Standard GObject macros */
+#define XCAMEL_TYPE_MESSAGE_INFO_BASE \
+       (xcamel_message_info_base_get_type ())
+#define XCAMEL_MESSAGE_INFO_BASE(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), XCAMEL_TYPE_MESSAGE_INFO_BASE, xCamelMessageInfoBase))
+#define XCAMEL_MESSAGE_INFO_BASE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), XCAMEL_TYPE_MESSAGE_INFO_BASE, xCamelMessageInfoBaseClass))
+#define XCAMEL_IS_MESSAGE_INFO_BASE(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), XCAMEL_TYPE_MESSAGE_INFO_BASE))
+#define XCAMEL_IS_MESSAGE_INFO_BASE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), XCAMEL_TYPE_MESSAGE_INFO_BASE))
+#define XCAMEL_MESSAGE_INFO_BASE_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), XCAMEL_TYPE_MESSAGE_INFO_BASE, xCamelMessageInfoBaseClass))
+
+G_BEGIN_DECLS
+
+typedef struct _xCamelMessageInfoBase xCamelMessageInfoBase;
+typedef struct _xCamelMessageInfoBaseClass xCamelMessageInfoBaseClass;
+typedef struct _xCamelMessageInfoBasePrivate xCamelMessageInfoBasePrivate;
+
+struct _xCamelMessageInfoBase {
+       xCamelMessageInfo parent;
+       xCamelMessageInfoBasePrivate *priv;
+};
+
+struct _xCamelMessageInfoBaseClass {
+       xCamelMessageInfoClass parent_class;
+};
+
+GType          xcamel_message_info_base_get_type       (void);
+
+G_END_DECLS
+
+#endif /* XCAMEL_MESSAGE_INFO_BASE_H */
diff --git a/camel/camel-message-info.c b/camel/camel-message-info.c
index fe0717e..179b0e9 100644
--- a/camel/camel-message-info.c
+++ b/camel/camel-message-info.c
@@ -21,22 +21,116 @@
 
 #include <stdio.h>
 
+#include "camel-folder.h"
 #include "camel-folder-summary.h"
+#include "camel-message-info-base.h"
+#include "camel-string-utils.h"
 
 #include "camel-message-info.h"
 
 struct _xCamelMessageInfoPrivate {
-       GMutex property_lock;
+       GRecMutex property_lock;
 
-       GWeakRef summary; /* CamelFolderSummary * */
+       GWeakRef summary;       /* CamelFolderSummary * */
+       gboolean dirty;         /* whether requires save to local disk/summary */
+       const gchar *uid;       /* allocated in the string pool */
 };
 
 enum {
        PROP_0,
-       PROP_SUMMARY
+       PROP_SUMMARY,
+       PROP_DIRTY,
+       PROP_FOLDER_FLAGGED,
+       PROP_UID,
+       PROP_FLAGS,
+       PROP_USER_FLAGS,
+       PROP_USER_TAGS,
+       PROP_SUBJECT,
+       PROP_PREVIEW,
+       PROP_FROM,
+       PROP_TO,
+       PROP_CC,
+       PROP_MLIST,
+       PROP_SIZE,
+       PROP_DATE_SENT,
+       PROP_DATE_RECEIVED,
+       PROP_MESSAGE_ID,
+       PROP_REFERENCES,
+       PROP_HEADERS
 };
 
-G_DEFINE_TYPE (xCamelMessageInfo, xcamel_message_info, G_TYPE_OBJECT)
+G_DEFINE_ABSTRACT_TYPE (xCamelMessageInfo, xcamel_message_info, G_TYPE_OBJECT)
+
+static xCamelMessageInfo *
+message_info_clone (const xCamelMessageInfo *mi,
+                   CamelFolderSummary *assign_summary)
+{
+       xCamelMessageInfo *result;
+       const gchar *uid;
+       const GArray *references;
+       const CamelNameValueArray *headers;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+       if (assign_summary)
+               g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (assign_summary), NULL);
+
+       /* Make sure the 'mi' doesn't change while copying the values. */
+       xcamel_message_info_property_lock (mi);
+
+       result = xcamel_message_info_new (assign_summary);
+
+       g_object_freeze_notify (G_OBJECT (result));
+
+       uid = xcamel_message_info_pooldup_uid (mi);
+       xcamel_message_info_set_uid (result, uid);
+       camel_pstring_free (uid);
+
+       xcamel_message_info_take_user_flags (result, xcamel_message_info_dup_user_flags (mi));
+       xcamel_message_info_take_user_tags (result, xcamel_message_info_dup_user_tags (mi));
+       xcamel_message_info_set_subject (result, xcamel_message_info_get_subject (mi));
+       xcamel_message_info_set_preview (result, xcamel_message_info_get_preview (mi));
+       xcamel_message_info_set_from (result, xcamel_message_info_get_from (mi));
+       xcamel_message_info_set_to (result, xcamel_message_info_get_to (mi));
+       xcamel_message_info_set_cc (result, xcamel_message_info_get_cc (mi));
+       xcamel_message_info_set_mlist (result, xcamel_message_info_get_mlist (mi));
+       xcamel_message_info_set_size (result, xcamel_message_info_get_size (mi));
+       xcamel_message_info_set_date_sent (result, xcamel_message_info_get_date_sent (mi));
+       xcamel_message_info_set_date_received (result, xcamel_message_info_get_date_received (mi));
+       xcamel_message_info_set_message_id (result, xcamel_message_info_get_message_id (mi));
+
+       references = xcamel_message_info_get_references (mi);
+       if (references && references->len) {
+               GArray *copy;
+               guint ii;
+
+               copy = g_array_sized_new (FALSE, FALSE, references->len, sizeof (guint64));
+
+               for (ii = 0; ii < references->len; ii++) {
+                       g_array_append_val (copy, g_array_index (references, guint64, ii));
+               }
+
+               xcamel_message_info_take_references (result, copy);
+       }
+
+       headers = xcamel_message_info_get_headers (mi);
+       if (headers) {
+               xcamel_message_info_take_headers (result,
+                       camel_name_value_array_copy (headers));
+       }
+
+       /* Set flags as the last, to not overwrite 'folder-flagged' flag by
+          the "changes" when copying fields. */
+       xcamel_message_info_set_flags (result, ~0, xcamel_message_info_get_flags (mi));
+
+       xcamel_message_info_property_unlock (mi);
+
+       /* Also ensure 'dirty' flag, thus it can be eventually saved. */
+       xcamel_message_info_set_dirty (result, TRUE);
+
+       g_object_thaw_notify (G_OBJECT (result));
+
+       return result;
+}
 
 static void
 message_info_set_property (GObject *object,
@@ -47,9 +141,81 @@ message_info_set_property (GObject *object,
        xCamelMessageInfo *mi = XCAMEL_MESSAGE_INFO (object);
 
        switch (property_id) {
-               case PROP_SUMMARY:
-                       g_weak_ref_set (&mi->priv->summary, g_value_get_object (value));
-                       return;
+       case PROP_SUMMARY:
+               g_weak_ref_set (&mi->priv->summary, g_value_get_object (value));
+               return;
+
+       case PROP_DIRTY:
+               xcamel_message_info_set_dirty (mi, g_value_get_boolean (value));
+               return;
+
+       case PROP_FOLDER_FLAGGED:
+               xcamel_message_info_set_folder_flagged (mi, g_value_get_boolean (value));
+               return;
+
+       case PROP_UID:
+               xcamel_message_info_set_uid (mi, g_value_get_string (value));
+               return;
+
+       case PROP_FLAGS:
+               xcamel_message_info_set_flags (mi, ~0, g_value_get_uint (value));
+               return;
+
+       case PROP_USER_FLAGS:
+               xcamel_message_info_take_user_flags (mi, g_value_dup_boxed (value));
+               return;
+
+       case PROP_USER_TAGS:
+               xcamel_message_info_take_user_tags (mi, g_value_dup_boxed (value));
+               return;
+
+       case PROP_SUBJECT:
+               xcamel_message_info_set_subject (mi, g_value_get_string (value));
+               return;
+
+       case PROP_PREVIEW:
+               xcamel_message_info_set_preview (mi, g_value_get_string (value));
+               return;
+
+       case PROP_FROM:
+               xcamel_message_info_set_from (mi, g_value_get_string (value));
+               return;
+
+       case PROP_TO:
+               xcamel_message_info_set_to (mi, g_value_get_string (value));
+               return;
+
+       case PROP_CC:
+               xcamel_message_info_set_cc (mi, g_value_get_string (value));
+               return;
+
+       case PROP_MLIST:
+               xcamel_message_info_set_mlist (mi, g_value_get_string (value));
+               return;
+
+       case PROP_SIZE:
+               xcamel_message_info_set_size (mi, g_value_get_uint (value));
+               return;
+
+       case PROP_DATE_SENT:
+               xcamel_message_info_set_date_sent (mi, g_value_get_int64 (value));
+               return;
+
+       case PROP_DATE_RECEIVED:
+               xcamel_message_info_set_date_received (mi, g_value_get_int64 (value));
+               return;
+
+       case PROP_MESSAGE_ID:
+               xcamel_message_info_set_message_id (mi, g_value_get_uint64 (value));
+               return;
+
+       case PROP_REFERENCES:
+               xcamel_message_info_take_references (mi, g_value_dup_boxed (value));
+               return;
+
+       case PROP_HEADERS:
+               xcamel_message_info_take_headers (mi, g_value_dup_boxed (value));
+               return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -61,13 +227,84 @@ message_info_get_property (GObject *object,
                           GValue *value,
                           GParamSpec *pspec)
 {
+       xCamelMessageInfo *mi = XCAMEL_MESSAGE_INFO (object);
+
        switch (property_id) {
-               case PROP_SUMMARY:
-                       g_value_take_object (
-                               value,
-                               xcamel_message_info_ref_summary (
-                               XCAMEL_MESSAGE_INFO (object)));
-                       return;
+       case PROP_SUMMARY:
+               g_value_take_object (value, xcamel_message_info_ref_summary (mi));
+               return;
+
+       case PROP_DIRTY:
+               xcamel_message_info_set_dirty (mi, g_value_get_boolean (value));
+               return;
+
+       case PROP_FOLDER_FLAGGED:
+               xcamel_message_info_set_folder_flagged (mi, g_value_get_boolean (value));
+               return;
+
+       case PROP_UID:
+               g_value_set_string (value, xcamel_message_info_get_uid (mi));
+               return;
+
+       case PROP_FLAGS:
+               g_value_set_uint (value, xcamel_message_info_get_flags (mi));
+               return;
+
+       case PROP_USER_FLAGS:
+               g_value_take_boxed (value, xcamel_message_info_dup_user_flags (mi));
+               return;
+
+       case PROP_USER_TAGS:
+               g_value_take_boxed (value, xcamel_message_info_dup_user_tags (mi));
+               return;
+
+       case PROP_SUBJECT:
+               g_value_set_string (value, xcamel_message_info_get_subject (mi));
+               return;
+
+       case PROP_PREVIEW:
+               g_value_set_string (value, xcamel_message_info_get_preview (mi));
+               return;
+
+       case PROP_FROM:
+               g_value_set_string (value, xcamel_message_info_get_from (mi));
+               return;
+
+       case PROP_TO:
+               g_value_set_string (value, xcamel_message_info_get_to (mi));
+               return;
+
+       case PROP_CC:
+               g_value_set_string (value, xcamel_message_info_get_cc (mi));
+               return;
+
+       case PROP_MLIST:
+               g_value_set_string (value, xcamel_message_info_get_mlist (mi));
+               return;
+
+       case PROP_SIZE:
+               g_value_set_uint (value, xcamel_message_info_get_size (mi));
+               return;
+
+       case PROP_DATE_SENT:
+               g_value_set_int64 (value, xcamel_message_info_get_date_sent (mi));
+               return;
+
+       case PROP_DATE_RECEIVED:
+               g_value_set_int64 (value, xcamel_message_info_get_date_received (mi));
+               return;
+
+       case PROP_MESSAGE_ID:
+               g_value_set_uint64 (value, xcamel_message_info_get_message_id (mi));
+               return;
+
+       case PROP_REFERENCES:
+               g_value_take_boxed (value, xcamel_message_info_dup_references (mi));
+               return;
+
+       case PROP_HEADERS:
+               g_value_take_boxed (value, xcamel_message_info_dup_headers (mi));
+               return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -79,6 +316,8 @@ message_info_dispose (GObject *object)
        xCamelMessageInfo *mi = XCAMEL_MESSAGE_INFO (object);
 
        g_weak_ref_set (&mi->priv->summary, NULL);
+       camel_pstring_free (mi->priv->uid);
+       mi->priv->uid = NULL;
 
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (xcamel_message_info_parent_class)->dispose (object);
@@ -90,7 +329,7 @@ message_info_finalize (GObject *object)
        xCamelMessageInfo *mi = XCAMEL_MESSAGE_INFO (object);
 
        g_weak_ref_clear (&mi->priv->summary);
-       g_mutex_clear (&mi->priv->property_lock);
+       g_rec_mutex_clear (&mi->priv->property_lock);
 
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (xcamel_message_info_parent_class)->finalize (object);
@@ -103,6 +342,8 @@ xcamel_message_info_class_init (xCamelMessageInfoClass *class)
 
        g_type_class_add_private (class, sizeof (xCamelMessageInfoPrivate));
 
+       class->clone = message_info_clone;
+
        object_class = G_OBJECT_CLASS (class);
        object_class->set_property = message_info_set_property;
        object_class->get_property = message_info_get_property;
@@ -113,6 +354,9 @@ xcamel_message_info_class_init (xCamelMessageInfoClass *class)
         * xCamelMessageInfo:summary
         *
         * The #CamelFolderSummary to which the message info belongs, or %NULL.
+        * It can be set only during construction of the object.
+        *
+        * Since: 3.24
         **/
        g_object_class_install_property (
                object_class,
@@ -120,10 +364,327 @@ xcamel_message_info_class_init (xCamelMessageInfoClass *class)
                g_param_spec_object (
                        "summary",
                        "Summary",
-                       "The summary to which the message info belongs",
+                       NULL,
                        CAMEL_TYPE_FOLDER_SUMMARY,
                        G_PARAM_READWRITE |
                        G_PARAM_CONSTRUCT_ONLY));
+
+       /**
+        * xCamelMessageInfo:uid
+        *
+        * A unique ID of the message in its folder.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_UID,
+               g_param_spec_string (
+                       "uid",
+                       "UID",
+                       NULL,
+                       NULL,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:dirty
+        *
+        * Flag, whether the info is changed and requires save to disk.
+        * Compare with CamelMessageInfo:folder-flagged
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_DIRTY,
+               g_param_spec_boolean (
+                       "dirty",
+                       "Dirty",
+                       NULL,
+                       FALSE,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:folder-flagged
+        *
+        * Flag, whether the info is changed and requires save to
+        * the destination store/server. This is different from
+        * the CamelMessageInfo:dirty, which takes care of the local
+        * information only.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_FOLDER_FLAGGED,
+               g_param_spec_boolean (
+                       "folder-flagged",
+                       "Folder Flagged",
+                       NULL,
+                       FALSE,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:flags
+        *
+        * Bit-or of #CamelMessageFlags.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_FLAGS,
+               g_param_spec_uint (
+                       "flags",
+                       "Flags",
+                       NULL,
+                       0, G_MAXUINT32, 0,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:user-flags
+        *
+        * User flags for the associated message. Can be %NULL.
+        * Unlike user-tags, which can contain various values, the user-flags
+        * can only be set or not.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_USER_FLAGS,
+               g_param_spec_boxed (
+                       "user-flags",
+                       "User Flags",
+                       NULL,
+                       CAMEL_TYPE_NAMED_FLAGS,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:user-tags
+        *
+        * User tags for the associated message. Can be %NULL.
+        * Unlike user-flags, which can be set or not, the user-tags
+        * can contain various values.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_USER_TAGS,
+               g_param_spec_boxed (
+                       "user-tags",
+                       "User tags",
+                       NULL,
+                       CAMEL_TYPE_NAME_VALUE_ARRAY,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:subject
+        *
+        * Subject of the associated message.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_SUBJECT,
+               g_param_spec_string (
+                       "subject",
+                       "Subject",
+                       NULL,
+                       NULL,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:preview
+        *
+        * Preview of the associated message.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_PREVIEW,
+               g_param_spec_string (
+                       "preview",
+                       "Preview",
+                       NULL,
+                       NULL,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:from
+        *
+        * From address of the associated message.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_FROM,
+               g_param_spec_string (
+                       "from",
+                       "From",
+                       NULL,
+                       NULL,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:to
+        *
+        * To address of the associated message.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_TO,
+               g_param_spec_string (
+                       "to",
+                       "To",
+                       NULL,
+                       NULL,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:cc
+        *
+        * CC address of the associated message.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_CC,
+               g_param_spec_string (
+                       "cc",
+                       "CC",
+                       NULL,
+                       NULL,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:mlist
+        *
+        * Mailing list address of the associated message.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_MLIST,
+               g_param_spec_string (
+                       "mlist",
+                       "mlist",
+                       NULL,
+                       NULL,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:size
+        *
+        * Size of the associated message.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_SIZE,
+               g_param_spec_uint (
+                       "size",
+                       "Size",
+                       NULL,
+                       0, G_MAXUINT32, 0,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:date-sent
+        *
+        * Sent Date of the associated message.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_DATE_SENT,
+               g_param_spec_int64 (
+                       "date-sent",
+                       "Date Sent",
+                       NULL,
+                       G_MININT64, G_MAXINT64, 0,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:date-received
+        *
+        * Received date of the associated message.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_DATE_RECEIVED,
+               g_param_spec_int64 (
+                       "date-received",
+                       "Date Received",
+                       NULL,
+                       G_MININT64, G_MAXINT64, 0,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:message-id
+        *
+        * Encoded Message-ID of the associated message as a guint64 number,
+        * partial MD5 sum. The value can be cast to #CamelSummaryMessageID.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_MESSAGE_ID,
+               g_param_spec_uint64 (
+                       "message-id",
+                       "Message ID",
+                       NULL,
+                       0, G_MAXUINT64, 0,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:references
+        *
+        * Encoded In-Reply-To and References headers of the associated message
+        * as an array of guint64 numbers, partial MD5 sums. Each value can be
+        * cast to #CamelSummaryMessageID.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_REFERENCES,
+               g_param_spec_boxed (
+                       "references",
+                       "References",
+                       NULL,
+                       G_TYPE_ARRAY,
+                       G_PARAM_READWRITE));
+
+       /**
+        * xCamelMessageInfo:headers
+        *
+        * Headers of the associated message. Can be %NULL.
+        *
+        * Since: 3.24
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_HEADERS,
+               g_param_spec_boxed (
+                       "headers",
+                       "Headers",
+                       NULL,
+                       CAMEL_TYPE_NAME_VALUE_ARRAY,
+                       G_PARAM_READWRITE));
 }
 
 static void
@@ -131,7 +692,7 @@ xcamel_message_info_init (xCamelMessageInfo *mi)
 {
        mi->priv = G_TYPE_INSTANCE_GET_PRIVATE (mi, XCAMEL_TYPE_MESSAGE_INFO, xCamelMessageInfoPrivate);
 
-       g_mutex_init (&mi->priv->property_lock);
+       g_rec_mutex_init (&mi->priv->property_lock);
        g_weak_ref_init (&mi->priv->summary, NULL);
 }
 
@@ -148,7 +709,50 @@ xcamel_message_info_init (xCamelMessageInfo *mi)
 xCamelMessageInfo *
 xcamel_message_info_new (CamelFolderSummary *summary)
 {
-       return g_object_new (XCAMEL_TYPE_MESSAGE_INFO, "summary", summary, NULL);
+       GType type = XCAMEL_TYPE_MESSAGE_INFO_BASE;
+
+       if (summary) {
+               CamelFolderSummaryClass *klass;
+
+               g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (summary), NULL);
+
+               klass = CAMEL_FOLDER_SUMMARY_GET_CLASS (summary);
+               g_return_val_if_fail (klass != NULL, NULL);
+
+               type = klass->message_info_type;
+       }
+
+       return g_object_new (type, "summary", summary, NULL);
+}
+
+/**
+ * xcamel_message_info_clone:
+ * @mi: a #CamelMessageInfo to clone
+ * @assign_summary: (nullable): parent #CamelFolderSummary object, or %NULL, to set on the clone
+ *
+ * Clones the @mi as a new #CamelMessageInfo and eventually assigns
+ * a new #CamelFolderSummary to it. If it's not set, then the new
+ * clone will not have assigned any summary.
+ *
+ * Returns: (transfer-full): a new #xCamelMessageInfo object, clone of the @mi
+ *
+ * Since: 3.24
+ **/
+xCamelMessageInfo *
+xcamel_message_info_clone (const xCamelMessageInfo *mi,
+                          CamelFolderSummary *assign_summary)
+{
+       xCamelMessageInfoClass *klass;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+       if (assign_summary)
+               g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (assign_summary), NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->clone != NULL, NULL);
+
+       return klass->clone (mi, assign_summary);
 }
 
 /**
@@ -161,9 +765,1636 @@ xcamel_message_info_new (CamelFolderSummary *summary)
  * Since: 3.24
  **/
 CamelFolderSummary *
-xcamel_message_info_ref_summary (xCamelMessageInfo *mi)
+xcamel_message_info_ref_summary (const xCamelMessageInfo *mi)
 {
        g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
 
        return g_weak_ref_get (&mi->priv->summary);
 }
+
+/**
+ * xcamel_message_info_property_lock:
+ * @mi: a #xCamelMessageInfo
+ *
+ * Acquires a property lock, which is used to ensure thread safety
+ * when properties are changing. Release the lock with
+ * xcamel_message_info_property_unlock().
+ *
+ * Since: 3.24
+ **/
+void
+xcamel_message_info_property_lock (const xCamelMessageInfo *mi)
+{
+       g_return_if_fail (XCAMEL_IS_MESSAGE_INFO (mi));
+
+       g_rec_mutex_lock (&mi->priv->property_lock);
+}
+
+/**
+ * xcamel_message_info_property_unlock:
+ * @mi: a #xCamelMessageInfo
+ *
+ * Releases a property lock, previously acquired with
+ * xcamel_message_info_property_lock().
+ *
+ * Since: 3.24
+ **/
+void
+xcamel_message_info_property_unlock (const xCamelMessageInfo *mi)
+{
+       g_return_if_fail (XCAMEL_IS_MESSAGE_INFO (mi));
+
+       g_rec_mutex_unlock (&mi->priv->property_lock);
+}
+
+static void
+xcamel_message_info_update_summary_and_folder (xCamelMessageInfo *mi,
+                                              gboolean update_counts)
+{
+       CamelFolderSummary *summary;
+
+       g_return_if_fail (XCAMEL_IS_MESSAGE_INFO (mi));
+
+       summary = xcamel_message_info_ref_summary (mi);
+       if (summary) {
+               CamelFolder *folder;
+
+               if (update_counts) {
+                       camel_folder_summary_lock (summary);
+                       g_object_freeze_notify (G_OBJECT (summary));
+
+                       camel_folder_summary_replace_flags (summary, mi);
+
+                       g_object_thaw_notify (G_OBJECT (summary));
+                       camel_folder_summary_unlock (summary);
+               }
+
+               folder = camel_folder_summary_get_folder (summary);
+               if (folder) {
+                       const gchar *uid;
+
+                       uid = xcamel_message_info_pooldup_uid (mi);
+                       if (uid) {
+                               CamelFolderChangeInfo *changes = camel_folder_change_info_new ();
+
+                               camel_folder_change_info_change_uid (changes, uid);
+                               camel_folder_changed (folder, changes);
+                               camel_folder_change_info_free (changes);
+                               camel_pstring_free (uid);
+                       }
+               }
+
+               g_clear_object (&summary);
+       }
+}
+
+/**
+ * xcamel_message_info_get_dirty:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: Whether the @mi is dirty, which means that it had been
+ *   changed and a save to the local summary is required.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_get_dirty (const xCamelMessageInfo *mi)
+{
+       gboolean result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       result = mi->priv->dirty;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_dirty:
+ * @mi: a #CamelMessageInfo
+ * @dirty: a dirty state to set
+ *
+ * Marks the @mi as dirty, which means a save to the local summary
+ * is required.
+ *
+ * Since: 3.24
+ **/
+void
+xcamel_message_info_set_dirty (const xCamelMessageInfo *mi,
+                              gboolean dirty)
+{
+       gboolean changed;
+       g_return_if_fail (XCAMEL_IS_MESSAGE_INFO (mi));
+
+       xcamel_message_info_property_lock (mi);
+       changed = (!mi->priv->dirty) != (!dirty);
+       if (changed)
+               mi->priv->dirty = dirty;
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "dirty");
+
+               if (dirty) {
+                       CamelFolderSummary *summary;
+
+                       summary = xcamel_message_info_ref_summary (mi);
+                       if (summary)
+                               camel_folder_summary_touch (summary);
+
+                       g_clear_object (&summary);
+               }
+
+       }
+}
+
+/**
+ * xcamel_message_info_get_folder_flagged:
+ * @mi: a #CamelMessageInfo
+ *
+ * The folder flagged flag is used to mark the message infor as being changed
+ * and this change should be propagated to the remote store (server). This is
+ * different from the 'dirty' flag, which is set for local changes only. It
+ * can happen that the 'folder-flagged' flag is set, but the 'dirty' flag not.
+ *
+ * This is only a convenient wrapper around CAMEL_MESSAGE_FOLDER_FLAGGED flag,
+ * for better readiness of the code.
+ *
+ * Returns: Whether requires save of the local changes into the remote store.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_get_folder_flagged (const xCamelMessageInfo *mi)
+{
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       return (xcamel_message_info_get_flags (mi) & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0;
+}
+
+/**
+ * xcamel_message_info_set_folder_flagged:
+ * @mi: a #CamelMessageInfo
+ * folder_flagged: a value to set to
+ *
+ * Changes the folder-flagged flag to the @folder_flagged value. See
+ * xcamel_message_info_get_folder_flagged() for more information about
+ * the use of this flag.
+ *
+ * This is only a convenient wrapper around CAMEL_MESSAGE_FOLDER_FLAGGED flag,
+ * for better readiness of the code.
+ *
+ * Returns: Whether the flag had been changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_folder_flagged (xCamelMessageInfo *mi,
+                                       gboolean folder_flagged)
+{
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       /* g_object_notify (G_OBJECT (mi), "folder-flagged");
+          is called as part of the set_flags function */
+
+       return xcamel_message_info_set_flags (mi, CAMEL_MESSAGE_FOLDER_FLAGGED,
+               folder_flagged ? CAMEL_MESSAGE_FOLDER_FLAGGED : 0);
+}
+
+/**
+ * xcamel_message_info_get_uid:
+ * @mi: a #CamelMessageInfo
+ *
+ * Get the UID of the #mi.
+ *
+ * Returns: (transfer none): The UID of the @mi.
+ *
+ * Since: 3.24
+ **/
+const gchar *
+xcamel_message_info_get_uid (const xCamelMessageInfo *mi)
+{
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = mi->priv->uid;
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_pooldup_uid:
+ * @mi: a #CamelMessageInfo
+ *
+ * Get the UID of the #mi, duplicated on the Camel's string pool.
+ * This is good for thread safety, though the UID should not change once set.
+ *
+ * Returns: A newly references string in the string pool, the #mi UID.
+ *   Free it with camel_pstring_free() when no longer needed.
+ *
+ * Since: 3.24
+ **/
+const gchar *
+xcamel_message_info_pooldup_uid (const xCamelMessageInfo *mi)
+{
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = camel_pstring_strdup (mi->priv->uid);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_uid:
+ * @mi: a #CamelMessageInfo
+ * @uid: a UID to set
+ *
+ * Changes UID of the @mi to @uid. If it changes, the 'dirty' flag
+ * of the @mi is set too. This change does not influence the 'folder-flagged'
+ * flag.
+ *
+ * Returns: Whether the UID changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_uid (xCamelMessageInfo *mi,
+                            const gchar *uid)
+{
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = mi->priv->uid != uid && g_strcmp0 (mi->priv->uid, uid) != 0;
+       if (changed) {
+               camel_pstring_free (mi->priv->uid);
+               mi->priv->uid = camel_pstring_strdup (uid);
+       }
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "uid");
+               xcamel_message_info_set_dirty (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_flags:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: Bit-or of #CamelMessageFlags set on the @mi.
+ *
+ * Since: 3.24
+ **/
+guint32
+xcamel_message_info_get_flags (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       guint32 result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), 0);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, 0);
+       g_return_val_if_fail (klass->get_flags != NULL, 0);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_flags (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_flags:
+ * @mi: a #CamelMessageInfo
+ * @mask: mask of flags to change
+ * @set: state the flags should be changed to
+ *
+ * Change the state of the flags on the @mi.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is also emitted folder's "changed" signal
+ * for this @mi, if necessary.
+ *
+ * Returns: Whether the flags changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_flags (xCamelMessageInfo *mi,
+                              CamelMessageFlags mask,
+                              guint32 set)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_flags != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_flags (mi, mask, set);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "flags");
+               xcamel_message_info_set_dirty (mi, TRUE);
+
+               /* Only if the folder-flagged was not part of the change */
+               if (!(mask & CAMEL_MESSAGE_FOLDER_FLAGGED))
+                       xcamel_message_info_set_folder_flagged (mi, TRUE);
+               else
+                       g_object_notify (G_OBJECT (mi), "folder-flagged");
+
+               xcamel_message_info_update_summary_and_folder (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_user_flag:
+ * @mi: a #CamelMessageInfo
+ * @name: user flag name
+ *
+ * Returns: Whther the user flag named @name is set.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_get_user_flag (const xCamelMessageInfo *mi,
+                                  const gchar *name)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->get_user_flag != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_user_flag (mi, name);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_user_flag:
+ * @mi: a #CamelMessageInfo
+ * @name: user flag name
+ * @state: state to set for the flag
+ *
+ * Change @state of the flag named @name. Unlike user tags, user flags
+ * can only be set or unset, while the user tags can contain certain values.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is also emitted folder's "changed" signal
+ * for this @mi, if necessary.
+ *
+ * Returns: Whether the message info changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_user_flag (xCamelMessageInfo *mi,
+                                  const gchar *name,
+                                  gboolean state)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_user_flag != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_user_flag (mi, name, state);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "user-flags");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+
+               xcamel_message_info_update_summary_and_folder (mi, FALSE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_dup_user_flags:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: (transfer full): A newly allocated #CamelNamedFlags with all the currently set
+ *   user flags on the @mi. Free the returned structure with camel_named_flags_free()
+ *   when no londer needed.
+ *
+ * Since: 3.24
+ **/
+CamelNamedFlags *
+xcamel_message_info_dup_user_flags (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       CamelNamedFlags *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->dup_user_flags != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->dup_user_flags (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_take_user_flags:
+ * @mi: a #CamelMessageInfo
+ * @user_flags: (transfer full): (nullable): user flags to set
+ *
+ * Takes all the @user_flags, which replaces any current user flags on the @mi.
+ * The passed-in @user_flags is consumed by the @mi, which becomes an owner
+ * of it. The caller should not change @user_flags afterwards.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is also emitted folder's "changed" signal
+ * for this @mi, if necessary.
+ *
+ * Returns: Whether the message info changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_take_user_flags (xCamelMessageInfo *mi,
+                                    CamelNamedFlags *user_flags)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->take_user_flags != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->take_user_flags (mi, user_flags);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "user-flags");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+
+               xcamel_message_info_update_summary_and_folder (mi, FALSE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_user_tag:
+ * @mi: a #CamelMessageInfo
+ * @name: user tag name
+ *
+ * Returns: (transfer none): (nullable): Value of the user tag, or %NULL when
+ *   it is not set.
+ *
+ * Since: 3.24
+ **/
+const gchar *
+xcamel_message_info_get_user_tag (const xCamelMessageInfo *mi,
+                                 const gchar *name)
+{
+       xCamelMessageInfoClass *klass;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+       g_return_val_if_fail (name != NULL, NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->get_user_tag != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_user_tag (mi, name);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_dup_user_tag:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: (transfer full): (nullable): Value of the user tag as newly allocated
+ *   string, or %NULL when it is not set. Free it with g_free() when no longer needed.
+ *
+ * Since: 3.24
+ **/
+gchar *
+xcamel_message_info_dup_user_tag (const xCamelMessageInfo *mi,
+                                 const gchar *name)
+{
+       gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+       g_return_val_if_fail (name != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = g_strdup (xcamel_message_info_get_user_tag (mi, name));
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_user_tag:
+ * @mi: a #CamelMessageInfo
+ * @name: user tag name
+ * @value: (nullable): user tag value, or %NULL to remove the user tag
+ *
+ * Set user tag @name to @value, or remove it, if @value is %NULL.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is also emitted folder's "changed" signal
+ * for this @mi, if necessary.
+ *
+ * Returns: Whether the @mi changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_user_tag (xCamelMessageInfo *mi,
+                                 const gchar *name,
+                                 const gchar *value)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_user_tag != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_user_tag (mi, name, value);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "user-tags");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+
+               xcamel_message_info_update_summary_and_folder (mi, FALSE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_dup_user_tags:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: (transfer full): a newly allocated #CamelNameValueArray containing all set
+ *   user tags of the @mi. Free it with camel_name_value_array_free() when no longer needed.
+ *
+ * Since: 3.24
+ **/
+CamelNameValueArray *
+xcamel_message_info_dup_user_tags (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       CamelNameValueArray *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->dup_user_tags != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->dup_user_tags (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_take_user_tags:
+ * @mi: a #CamelMessageInfo
+ * @user_tags: (transfer full): (nullable): user tags to set
+ *
+ * Takes all the @user_tags, which replaces any current user tags on the @mi.
+ * The passed-in @user_tags is consumed by the @mi, which becomes an owner
+ * of it. The caller should not change @user_tags afterwards.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is also emitted folder's "changed" signal
+ * for this @mi, if necessary.
+ *
+ * Returns: Whether the @mi changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_take_user_tags (xCamelMessageInfo *mi,
+                                   CamelNameValueArray *user_tags)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->take_user_tags != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->take_user_tags (mi, user_tags);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "user-tags");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+
+               xcamel_message_info_update_summary_and_folder (mi, FALSE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_subject:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: (transfer none): Subject of the #mi.
+ *
+ * Since: 3.24
+ **/
+const gchar *
+xcamel_message_info_get_subject (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->get_subject != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_subject (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_subject:
+ * @mi: a #xCamelMessageInfo
+ * @subject: (nullable): a Subject to set
+ *
+ * Sets Subject from the associated message.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_subject (xCamelMessageInfo *mi,
+                               const gchar *subject)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_subject != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_subject (mi, subject);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "subject");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_preview:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: (transfer none): Preview of the @mi.
+ *
+ * Since: 3.24
+ **/
+const gchar *
+xcamel_message_info_get_preview (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->get_preview != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_preview (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_preview:
+ * @mi: a #xCamelMessageInfo
+ * @preview: (nullable): a preview to set
+ *
+ * Sets preview of the associated message.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_preview (xCamelMessageInfo *mi,
+                               const gchar *preview)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_preview != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_preview (mi, preview);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "preview");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_from:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: (transfer none): From address of the @mi.
+ *
+ * Since: 3.24
+ **/
+const gchar *
+xcamel_message_info_get_from (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->get_from != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_from (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_from:
+ * @mi: a #xCamelMessageInfo
+ * @from: (nullable): a From to set
+ *
+ * Sets From from the associated message.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_from (xCamelMessageInfo *mi,
+                            const gchar *from)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_from != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_from (mi, from);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "from");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_to:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: (transfer none): To address of the @mi.
+ *
+ * Since: 3.24
+ **/
+const gchar *
+xcamel_message_info_get_to (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->get_to != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_to (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_to:
+ * @mi: a #xCamelMessageInfo
+ * @to: (nullable): a To to set
+ *
+ * Sets To from the associated message.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_to (xCamelMessageInfo *mi,
+                          const gchar *to)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_to != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_to (mi, to);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "to");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_cc:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: (transfer none): CC address of the @mi.
+ *
+ * Since: 3.24
+ **/
+const gchar *
+xcamel_message_info_get_cc (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->get_cc != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_cc (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_cc:
+ * @mi: a #xCamelMessageInfo
+ * @cc: (nullable): a CC to set
+ *
+ * Sets CC from the associated message.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_cc (xCamelMessageInfo *mi,
+                          const gchar *cc)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_cc != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_cc (mi, cc);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "cc");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_mlist:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: (transfer none): Mailing list address of the @mi.
+ *
+ * Since: 3.24
+ **/
+const gchar *
+xcamel_message_info_get_mlist (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       const gchar *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->get_mlist != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_mlist (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_mlist:
+ * @mi: a #xCamelMessageInfo
+ * @mlist: (nullable): a message list address to set
+ *
+ * Sets mesage list address from the associated message.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_mlist (xCamelMessageInfo *mi,
+                             const gchar *mlist)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_mlist != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_mlist (mi, mlist);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "mlist");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_size:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: Size of the associated message.
+ *
+ * Since: 3.24
+ **/
+guint32
+xcamel_message_info_get_size (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       guint32 result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), 0);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, 0);
+       g_return_val_if_fail (klass->get_size != NULL, 0);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_size (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_size:
+ * @mi: a #xCamelMessageInfo
+ * @size: a size to set
+ *
+ * Sets size of the associated message.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_size (xCamelMessageInfo *mi,
+                            guint32 size)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_size != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_size (mi, size);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "size");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_date_sent:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: time_t of the Date header of the message, encoded as gint64.
+ *
+ * Since: 3.24
+ **/
+gint64
+xcamel_message_info_get_date_sent (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       gint64 result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), 0);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, 0);
+       g_return_val_if_fail (klass->get_date_sent != NULL, 0);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_date_sent (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_date_sent:
+ * @mi: a #xCamelMessageInfo
+ * @date_sent: a sent date to set
+ *
+ * Sets sent date (the Date header) of the associated message.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_date_sent (xCamelMessageInfo *mi,
+                                 gint64 date_sent)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_date_sent != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_date_sent (mi, date_sent);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "date-sent");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_date_received:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: time_t of the Received header of the message, encoded as gint64.
+ *
+ * Since: 3.24
+ **/
+gint64
+xcamel_message_info_get_date_received (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       gint64 result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), 0);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, 0);
+       g_return_val_if_fail (klass->get_date_received != NULL, 0);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_date_received (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_date_received:
+ * @mi: a #xCamelMessageInfo
+ * @date_received: a received date to set
+ *
+ * Sets received date (the Received header) of the associated message.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_date_received (xCamelMessageInfo *mi,
+                                     gint64 date_received)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_date_received != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_date_received (mi, date_received);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "date-received");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_message_id:
+ * @mi: a #CamelMessageInfo
+ *
+ * Encoded Message-ID of the associated message as a guint64 number,
+ * partial MD5 sum. The value can be cast to #CamelSummaryMessageID.
+ *
+ * Returns: Partial MD5 hash of the Message-ID header of the associated message.
+ *
+ * Since: 3.24
+ **/
+guint64
+xcamel_message_info_get_message_id (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       guint64 result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), 0);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, 0);
+       g_return_val_if_fail (klass->get_message_id != NULL, 0);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_message_id (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_set_message_id:
+ * @mi: a #xCamelMessageInfo
+ * @message_id: a message id to set
+ *
+ * Sets encoded Message-ID of the associated message as a guint64 number,
+ * partial MD5 sum. The value can be cast to #CamelSummaryMessageID.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_set_message_id (xCamelMessageInfo *mi,
+                                  guint64 message_id)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->set_message_id != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->set_message_id (mi, message_id);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "message-id");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_references:
+ * @mi: a #CamelMessageInfo
+ *
+ * Gets encoded In-Reply-To and References headers of the associated
+ * message as an array of guint64 numbers, partial MD5 sums. Each value
+ * can be cast to #CamelSummaryMessageID.
+ *
+ * Returns: (transfer none): (nullable): A #GArray of guint64 encoded
+ *   Message-ID-s; or %NULL when none are available.
+ *
+ * Since: 3.24
+ **/
+const GArray *
+xcamel_message_info_get_references (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       const GArray *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->get_references != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_references (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_dup_references:
+ * @mi: a #CamelMessageInfo
+ *
+ * Duplicates encoded In-Reply-To and References headers of the associated
+ * message as an array of guint64 numbers, partial MD5 sums. Each value
+ * can be cast to #CamelSummaryMessageID.
+ *
+ * Returns: (transfer full): (nullable): A #GArray of guint64 encoded
+ *   Message-ID-s; or %NULL when none are available. Free returned array
+ *   with g_array_unref() when no longer needed.
+ *
+ * Since: 3.24
+ **/
+GArray *
+xcamel_message_info_dup_references (const xCamelMessageInfo *mi)
+{
+       const GArray *arr;
+       GArray *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       xcamel_message_info_property_lock (mi);
+       arr = xcamel_message_info_get_references (mi);
+       if (arr) {
+               guint ii;
+
+               result = g_array_sized_new (FALSE, FALSE, sizeof (guint64), arr->len);
+               for (ii = 0; ii < arr->len; ii++) {
+                       g_array_append_val (result, g_array_index (arr, guint64, ii));
+               }
+       } else {
+               result = NULL;
+       }
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_take_references:
+ * @mi: a #xCamelMessageInfo
+ * @references: (transfer full): (nullable): a references to set
+ *
+ * Takes encoded In-Reply-To and References headers of the associated message
+ * as an array of guint64 numbers, partial MD5 sums. Each value can be
+ * cast to #CamelSummaryMessageID.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_take_references (xCamelMessageInfo *mi,
+                                   GArray *references)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->take_references != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->take_references (mi, references);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "references");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
+
+/**
+ * xcamel_message_info_get_headers:
+ * @mi: a #CamelMessageInfo
+ *
+ * Returns: (transfer none): (nullable): All the message headers of the associated
+ *   message, or %NULL, when none are available.
+ *
+ * Since: 3.24
+ **/
+const CamelNameValueArray *
+xcamel_message_info_get_headers (const xCamelMessageInfo *mi)
+{
+       xCamelMessageInfoClass *klass;
+       const CamelNameValueArray *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, NULL);
+       g_return_val_if_fail (klass->get_headers != NULL, NULL);
+
+       xcamel_message_info_property_lock (mi);
+       result = klass->get_headers (mi);
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_dup_headers:
+ * @mi: a #CamelMessageInfo
+ *
+ * Duplicates array of headers for the @mi.
+ *
+ * Returns: (transfer full): (nullable): All the message headers of the associated
+ *   message, or %NULL, when none are available. Free returned array with
+ *   camel_name_value_array_free() when no longer needed.
+ *
+ * Since: 3.24
+ **/
+CamelNameValueArray *
+xcamel_message_info_dup_headers (const xCamelMessageInfo *mi)
+{
+       const CamelNameValueArray *arr;
+       CamelNameValueArray *result;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), NULL);
+
+       xcamel_message_info_property_lock (mi);
+       arr = xcamel_message_info_get_headers (mi);
+       if (arr) {
+               result = camel_name_value_array_copy (arr);
+       } else {
+               result = NULL;
+       }
+       xcamel_message_info_property_unlock (mi);
+
+       return result;
+}
+
+/**
+ * xcamel_message_info_take_headers:
+ * @mi: a #xCamelMessageInfo
+ * @headers: (transfer full): (nullable): headers to set, as #CamelNameValueArray, or %NULL
+ *
+ * Takes headers of the associated message.
+ *
+ * This property is considered static, in a meaning that it should
+ * not change during the life-time of the @mi, the same as it doesn't
+ * change in the associated message.
+ *
+ * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
+ * set automatically. There is not emitted folder's "changed" signal
+ * for this @mi.
+ *
+ * Returns: Whether the value changed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+xcamel_message_info_take_headers (xCamelMessageInfo *mi,
+                                CamelNameValueArray *headers)
+{
+       xCamelMessageInfoClass *klass;
+       gboolean changed;
+
+       g_return_val_if_fail (XCAMEL_IS_MESSAGE_INFO (mi), FALSE);
+
+       klass = XCAMEL_MESSAGE_INFO_GET_CLASS (mi);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->take_headers != NULL, FALSE);
+
+       xcamel_message_info_property_lock (mi);
+       changed = klass->take_headers (mi, headers);
+       xcamel_message_info_property_unlock (mi);
+
+       if (changed) {
+               g_object_notify (G_OBJECT (mi), "headers");
+               xcamel_message_info_set_dirty (mi, TRUE);
+               xcamel_message_info_set_folder_flagged (mi, TRUE);
+       }
+
+       return changed;
+}
diff --git a/camel/camel-message-info.h b/camel/camel-message-info.h
index 2fc20cf..5863d1d 100644
--- a/camel/camel-message-info.h
+++ b/camel/camel-message-info.h
@@ -24,6 +24,8 @@
 
 #include <glib-object.h>
 
+#include <camel/camel-mime-utils.h>
+
 /* Standard GObject macros */
 #define XCAMEL_TYPE_MESSAGE_INFO \
        (xcamel_message_info_get_type ())
@@ -48,6 +50,48 @@ G_BEGIN_DECLS
 /* Forward declarations */
 struct _CamelFolderSummary;
 
+/* A summary messageid is a 64 bit identifier (partial md5 hash) */
+typedef struct _CamelSummaryMessageID {
+       union {
+               guint64 id;
+               guchar hash[8];
+               struct {
+                       guint32 hi;
+                       guint32 lo;
+               } part;
+       } id;
+} CamelSummaryMessageID;
+
+/* system flag bits */
+typedef enum _CamelMessageFlags {
+       CAMEL_MESSAGE_ANSWERED = 1 << 0,
+       CAMEL_MESSAGE_DELETED = 1 << 1,
+       CAMEL_MESSAGE_DRAFT = 1 << 2,
+       CAMEL_MESSAGE_FLAGGED = 1 << 3,
+       CAMEL_MESSAGE_SEEN = 1 << 4,
+
+       /* these aren't really system flag bits, but are convenience flags */
+       CAMEL_MESSAGE_ATTACHMENTS = 1 << 5,
+       CAMEL_MESSAGE_ANSWERED_ALL = 1 << 6,
+       CAMEL_MESSAGE_JUNK = 1 << 7,
+       CAMEL_MESSAGE_SECURE = 1 << 8,
+       CAMEL_MESSAGE_NOTJUNK = 1 << 9,
+       CAMEL_MESSAGE_FORWARDED = 1 << 10,
+
+       /* following flags are for the folder, and are not really permanent flags */
+       CAMEL_MESSAGE_FOLDER_FLAGGED = 1 << 16, /* for use by the folder implementation */
+       /* flags after 1 << 16 are used by camel providers,
+        * if adding non permanent flags, add them to the end  */
+
+       CAMEL_MESSAGE_JUNK_LEARN = 1 << 30, /* used when setting CAMEL_MESSAGE_JUNK flag
+                                            * to say that we request junk plugin
+                                            * to learn that message as junk/non junk */
+       CAMEL_MESSAGE_USER = 1 << 31 /* supports user flags */
+} CamelMessageFlags;
+
+/* Changes to system flags will NOT trigger a folder changed event */
+#define CAMEL_MESSAGE_SYSTEM_MASK (0xffff << 16)
+
 typedef struct _xCamelMessageInfo xCamelMessageInfo;
 typedef struct _xCamelMessageInfoClass xCamelMessageInfoClass;
 typedef struct _xCamelMessageInfoPrivate xCamelMessageInfoPrivate;
@@ -59,13 +103,189 @@ struct _xCamelMessageInfo {
 
 struct _xCamelMessageInfoClass {
        GObjectClass parent_class;
+
+       xCamelMessageInfo *     (* clone)       (const xCamelMessageInfo *mi,
+                                                struct _CamelFolderSummary *summary);
+       guint32                 (* get_flags)   (const xCamelMessageInfo *mi);
+       gboolean                (* set_flags)   (xCamelMessageInfo *mi,
+                                                CamelMessageFlags flags,
+                                                guint32 set);
+       gboolean                (* get_user_flag)
+                                               (const xCamelMessageInfo *mi,
+                                                const gchar *name);
+       gboolean                (* set_user_flag)
+                                               (xCamelMessageInfo *mi,
+                                                const gchar *name,
+                                                gboolean state);
+       CamelNamedFlags *       (* dup_user_flags)
+                                               (const xCamelMessageInfo *mi);
+       gboolean                (* take_user_flags)
+                                               (xCamelMessageInfo *mi,
+                                                CamelNamedFlags *user_flags);
+       const gchar *           (* get_user_tag)(const xCamelMessageInfo *mi,
+                                                const gchar *name);
+       gboolean                (* set_user_tag)(xCamelMessageInfo *mi,
+                                                const gchar *name,
+                                                const gchar *value);
+       CamelNameValueArray *   (* dup_user_tags)
+                                               (const xCamelMessageInfo *mi);
+       gboolean                (* take_user_tags)
+                                               (xCamelMessageInfo *mi,
+                                                CamelNameValueArray *user_tags);
+       const gchar *           (* get_subject) (const xCamelMessageInfo *mi);
+       gboolean                (* set_subject) (xCamelMessageInfo *mi,
+                                                const gchar *subject);
+       const gchar *           (* get_preview) (const xCamelMessageInfo *mi);
+       gboolean                (* set_preview) (xCamelMessageInfo *mi,
+                                                const gchar *preview);
+       const gchar *           (* get_from)    (const xCamelMessageInfo *mi);
+       gboolean                (* set_from)    (xCamelMessageInfo *mi,
+                                                const gchar *from);
+       const gchar *           (* get_to)      (const xCamelMessageInfo *mi);
+       gboolean                (* set_to)      (xCamelMessageInfo *mi,
+                                                const gchar *to);
+       const gchar *           (* get_cc)      (const xCamelMessageInfo *mi);
+       gboolean                (* set_cc)      (xCamelMessageInfo *mi,
+                                                const gchar *cc);
+       const gchar *           (* get_mlist)   (const xCamelMessageInfo *mi);
+       gboolean                (* set_mlist)   (xCamelMessageInfo *mi,
+                                                const gchar *mlist);
+       guint32                 (* get_size)    (const xCamelMessageInfo *mi);
+       gboolean                (* set_size)    (xCamelMessageInfo *mi,
+                                                guint32 size);
+       gint64                  (* get_date_sent)
+                                               (const xCamelMessageInfo *mi);
+       gboolean                (* set_date_sent)
+                                               (xCamelMessageInfo *mi,
+                                                gint64 date_sent);
+       gint64                  (* get_date_received)
+                                               (const xCamelMessageInfo *mi);
+       gboolean                (* set_date_received)
+                                               (xCamelMessageInfo *mi,
+                                                gint64 date_received);
+       guint64                 (* get_message_id)
+                                               (const xCamelMessageInfo *mi);
+       gboolean                (* set_message_id)
+                                               (xCamelMessageInfo *mi,
+                                                guint64 message_id);
+       const GArray *          (* get_references)
+                                               (const xCamelMessageInfo *mi);
+       gboolean                (* take_references)
+                                               (xCamelMessageInfo *mi,
+                                                GArray *references);
+       const CamelNameValueArray *
+                               (* get_headers) (const xCamelMessageInfo *mi);
+       gboolean                (* take_headers)(xCamelMessageInfo *mi,
+                                               CamelNameValueArray *headers);
+
+       /* Padding for future expansion */
+       gpointer reserved[20];
 };
 
 GType          xcamel_message_info_get_type    (void);
 xCamelMessageInfo *
                xcamel_message_info_new         (struct _CamelFolderSummary *summary);
+xCamelMessageInfo *
+               xcamel_message_info_clone       (const xCamelMessageInfo *mi,
+                                                struct _CamelFolderSummary *summary);
 struct _CamelFolderSummary *
-               xcamel_message_info_ref_summary (xCamelMessageInfo *mi);
+               xcamel_message_info_ref_summary (const xCamelMessageInfo *mi);
+void           xcamel_message_info_property_lock
+                                               (const xCamelMessageInfo *mi);
+void           xcamel_message_info_property_unlock
+                                               (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_get_dirty   (const xCamelMessageInfo *mi);
+void           xcamel_message_info_set_dirty   (const xCamelMessageInfo *mi,
+                                                gboolean dirty);
+gboolean       xcamel_message_info_get_folder_flagged
+                                               (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_folder_flagged
+                                               (xCamelMessageInfo *mi,
+                                                gboolean folder_flagged);
+const gchar *  xcamel_message_info_get_uid     (const xCamelMessageInfo *mi);
+const gchar *  xcamel_message_info_pooldup_uid (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_uid     (xCamelMessageInfo *mi,
+                                                const gchar *uid);
+guint32                xcamel_message_info_get_flags   (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_flags   (xCamelMessageInfo *mi,
+                                                CamelMessageFlags mask,
+                                                guint32 set);
+gboolean       xcamel_message_info_get_user_flag
+                                               (const xCamelMessageInfo *mi,
+                                                const gchar *name);
+gboolean       xcamel_message_info_set_user_flag
+                                               (xCamelMessageInfo *mi,
+                                                const gchar *name,
+                                                gboolean state);
+CamelNamedFlags *
+               xcamel_message_info_dup_user_flags
+                                               (const xCamelMessageInfo *mi);
+gboolean
+               xcamel_message_info_take_user_flags
+                                               (xCamelMessageInfo *mi,
+                                                CamelNamedFlags *user_flags);
+const gchar *  xcamel_message_info_get_user_tag        (const xCamelMessageInfo *mi,
+                                                const gchar *name);
+gchar *                xcamel_message_info_dup_user_tag        (const xCamelMessageInfo *mi,
+                                                const gchar *name);
+gboolean       xcamel_message_info_set_user_tag        (xCamelMessageInfo *mi,
+                                                const gchar *name,
+                                                const gchar *value);
+CamelNameValueArray *
+               xcamel_message_info_dup_user_tags
+                                               (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_take_user_tags
+                                               (xCamelMessageInfo *mi,
+                                                CamelNameValueArray *user_tags);
+const gchar *  xcamel_message_info_get_subject (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_subject (xCamelMessageInfo *mi,
+                                                const gchar *subject);
+const gchar *  xcamel_message_info_get_preview (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_preview (xCamelMessageInfo *mi,
+                                                const gchar *preview);
+const gchar *  xcamel_message_info_get_from    (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_from    (xCamelMessageInfo *mi,
+                                                const gchar *from);
+const gchar *  xcamel_message_info_get_to      (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_to      (xCamelMessageInfo *mi,
+                                                const gchar *to);
+const gchar *  xcamel_message_info_get_cc      (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_cc      (xCamelMessageInfo *mi,
+                                                const gchar *cc);
+const gchar *  xcamel_message_info_get_mlist   (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_mlist   (xCamelMessageInfo *mi,
+                                                const gchar *mlist);
+guint32                xcamel_message_info_get_size    (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_size    (xCamelMessageInfo *mi,
+                                                guint32 size);
+gint64         xcamel_message_info_get_date_sent
+                                               (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_date_sent
+                                               (xCamelMessageInfo *mi,
+                                                gint64 date_sent);
+gint64         xcamel_message_info_get_date_received
+                                               (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_date_received
+                                               (xCamelMessageInfo *mi,
+                                                gint64 date_received);
+guint64                xcamel_message_info_get_message_id
+                                               (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_set_message_id
+                                               (xCamelMessageInfo *mi,
+                                                guint64 message_id);
+const GArray * xcamel_message_info_get_references
+                                               (const xCamelMessageInfo *mi);
+GArray *       xcamel_message_info_dup_references
+                                               (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_take_references
+                                               (xCamelMessageInfo *mi,
+                                                GArray *references);
+const CamelNameValueArray *
+               xcamel_message_info_get_headers (const xCamelMessageInfo *mi);
+CamelNameValueArray *
+               xcamel_message_info_dup_headers (const xCamelMessageInfo *mi);
+gboolean       xcamel_message_info_take_headers        (xCamelMessageInfo *mi,
+                                               CamelNameValueArray *headers);
 
 G_END_DECLS
 
diff --git a/camel/camel-mime-utils.c b/camel/camel-mime-utils.c
index 51ff904..8b66b60 100644
--- a/camel/camel-mime-utils.c
+++ b/camel/camel-mime-utils.c
@@ -43,6 +43,7 @@
 #include "camel-iconv.h"
 #include "camel-mime-utils.h"
 #include "camel-net-utils.h"
+#include "camel-string-utils.h"
 #ifdef G_OS_WIN32
 #include <winsock2.h>
 #include <ws2tcpip.h>
@@ -104,6 +105,827 @@ G_DEFINE_BOXED_TYPE (CamelHeaderAddress,
                camel_header_address_ref,
                camel_header_address_unref)
 
+G_DEFINE_BOXED_TYPE (CamelNameValueArray,
+               camel_name_value_array,
+               camel_name_value_array_copy,
+               camel_name_value_array_free)
+
+typedef struct _CamelNameValuePair {
+       gchar *name;
+       gchar *value;
+} CamelNameValuePair;
+
+static void
+free_name_value_content (gpointer ptr)
+{
+       CamelNameValuePair *pair = ptr;
+
+       if (pair) {
+               g_free (pair->name);
+               g_free (pair->value);
+
+               pair->name = NULL;
+               pair->value = NULL;
+       }
+}
+
+/**
+ * camel_name_value_array_new:
+ *
+ * Created a new #CamelNameValueArray. The returned pointer should be freed
+ * with camel_name_value_array_free() when no longer needed.
+ *
+ * Returns: (transfer-full): A new #CamelNameValueArray.
+ *
+ * See: camel_name_value_array_new_sized, camel_name_value_array_copy
+ *
+ * Since: 3.24
+ **/
+CamelNameValueArray *
+camel_name_value_array_new (void)
+{
+       GArray *arr;
+
+       arr = g_array_new (FALSE, FALSE, sizeof (CamelNameValuePair));
+       g_array_set_clear_func (arr, free_name_value_content);
+
+       return (CamelNameValueArray *) arr;
+}
+
+/**
+ * camel_name_value_array_new_sized:
+ * @reserve_size: an array size to reserve
+ *
+ * Created a new #CamelNameValueArray, which has reserved @reserve_size
+ * elements. This value doesn't influence the camel_name_value_array_length(),
+ * which returns zero on the array returned from this function. The returned
+ * pointer should be freed with camel_name_value_array_free() when no longer needed.
+ *
+ * Returns: (transfer-full): A new #CamelNameValueArray.
+ *
+ * See: camel_name_value_array_new, camel_name_value_array_copy
+ *
+ * Since: 3.24
+ **/
+CamelNameValueArray *
+camel_name_value_array_new_sized (guint reserve_size)
+{
+       GArray *arr;
+
+       arr = g_array_sized_new (FALSE, FALSE, sizeof (CamelNameValuePair), reserve_size);
+       g_array_set_clear_func (arr, free_name_value_content);
+
+       return (CamelNameValueArray *) arr;
+}
+
+/**
+ * camel_name_value_array_copy:
+ * @array: a #CamelNameValueArray
+ *
+ * Created a new copy of the @array. The returned pointer should be freed
+ * with camel_name_value_array_free() when no longer needed.
+ *
+ * Returns: (transfer-full): A new copy of the @array.
+ *
+ * See: camel_name_value_array_new, camel_name_value_array_new_sized
+ *
+ * Since: 3.24
+ **/
+CamelNameValueArray *
+camel_name_value_array_copy (const CamelNameValueArray *array)
+{
+       CamelNameValueArray *copy;
+       guint ii, len;
+
+       if (!array)
+               return NULL;
+
+       len = camel_name_value_array_length (array);
+       copy = camel_name_value_array_new_sized (len);
+
+       for (ii = 0; ii < len; ii++) {
+               const gchar *name = NULL, *value = NULL;
+
+               if (camel_name_value_array_get (array, ii, &name, &value))
+                       camel_name_value_array_append (copy, name, value);
+       }
+
+       return copy;
+}
+
+/**
+ * camel_name_value_array_free:
+ * @array: (nullable): a #CamelNameValueArray, or %NULL
+ *
+ * Frees the @array, previously allocated by camel_name_value_array_new(),
+ * camel_name_value_array_new_sized() or camel_name_value_array_copy().
+ * If the @array is %NULL, then does nothing.
+ *
+ * Since: 3.24
+ **/
+void
+camel_name_value_array_free (CamelNameValueArray *array)
+{
+       if (array) {
+               g_array_free ((GArray *) array, TRUE);
+       }
+}
+
+/**
+ * camel_name_value_array_length:
+ * @array: a #CamelNameValueArray
+ *
+ * Returns: Length of the @array, aka how many elements are stored in the @array.
+ *
+ * Since: 3.24
+ **/
+guint
+camel_name_value_array_length (const CamelNameValueArray *array)
+{
+       GArray *arr = (GArray *) array;
+
+       g_return_val_if_fail (array != NULL, 0);
+
+       return arr->len;
+}
+
+/**
+ * camel_name_value_array_get:
+ * @array: a #CamelNameValueArray
+ * @index: an index
+ * @out_name: (out): (nullable): A place to store the name of the element, or %NULL
+ * @out_value: (out): (nullable): A place to store the value of the element, or %NULL
+ *
+ * Returns the name and the value of the element at index @index. Either
+ * of the @out_name and @out_value can be %NULL, to not return that part.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise.
+ *
+ * See: camel_name_value_array_get_name, camel_name_value_array_get_value, camel_name_value_array_get_named
+ *
+ * Since: 3.24
+ **/
+gboolean
+camel_name_value_array_get (const CamelNameValueArray *array,
+                           guint index,
+                           const gchar **out_name,
+                           const gchar **out_value)
+{
+       GArray *arr = (GArray *) array;
+       CamelNameValuePair *pair;
+
+       g_return_val_if_fail (array != NULL, FALSE);
+       g_return_val_if_fail (index < camel_name_value_array_length (array), FALSE);
+
+       pair = &g_array_index (arr, CamelNameValuePair, index);
+
+       if (out_name)
+               *out_name = pair->name;
+       if (out_value)
+               *out_value= pair->value;
+
+       return TRUE;
+}
+
+static guint
+camel_name_value_array_find_named (const CamelNameValueArray *array,
+                                  gboolean case_sensitive,
+                                  const gchar *name)
+{
+       GArray *arr = (GArray *) array;
+       gint ii;
+
+       g_return_val_if_fail (array != NULL, (guint) -1);
+       g_return_val_if_fail (name != NULL, (guint) -1);
+
+       for (ii = 0; ii < arr->len; ii++) {
+               CamelNameValuePair *pair = &g_array_index (arr, CamelNameValuePair, ii);
+
+               if ((case_sensitive && g_strcmp0 (name, pair->name) == 0) ||
+                   (!case_sensitive && pair->name && camel_strcase_equal (name, pair->name))) {
+                       return ii;
+               }
+       }
+
+       return (guint) -1;
+}
+
+/**
+ * camel_name_value_array_set_named:
+ * @array: a #CamelNameValueArray
+ * @case_sensitive: whether to compare names case sensitively
+ * @name: a name
+ *
+ * Returns the value of the first element named @name, or %NULL when there
+ * is no element of such @name in the @array. The @case_sensitive determines
+ * whether compare names case sensitively (%TRUE) or insensitively (%FALSE).
+ *
+ * Returns: (transfer-none): (nullable): Value of the first element named @name, or %NULL.
+ *
+ * See: camel_name_value_array_get, camel_name_value_array_get_name
+ *
+ * Since: 3.24
+ **/
+const gchar *
+camel_name_value_array_get_named (const CamelNameValueArray *array,
+                                 gboolean case_sensitive,
+                                 const gchar *name)
+{
+       guint index;
+
+       g_return_val_if_fail (array != NULL, NULL);
+       g_return_val_if_fail (name != NULL, NULL);
+
+       index = camel_name_value_array_find_named (array, case_sensitive, name);
+       if (index == (guint) -1)
+               return NULL;
+
+       return camel_name_value_array_get_value (array, index);
+}
+
+/**
+ * camel_name_value_array_get_name:
+ * @array: a #CamelNameValueArray
+ * @index: an index
+ *
+ * Returns the name of the element at index @index.
+ *
+ * Returns: (transfer-none): (nullable): Name of the element at the given @index,
+ *    or %NULL on error.
+ *
+ * See: camel_name_value_array_get, camel_name_value_array_get_value
+ *
+ * Since: 3.24
+ **/
+const gchar *
+camel_name_value_array_get_name (const CamelNameValueArray *array,
+                                guint index)
+{
+       const gchar *name = NULL;
+
+       g_return_val_if_fail (array != NULL, NULL);
+       g_return_val_if_fail (index < camel_name_value_array_length (array), NULL);
+
+       if (!camel_name_value_array_get (array, index, &name, NULL))
+               return NULL;
+
+       return name;
+}
+
+/**
+ * camel_name_value_array_get_value:
+ * @array: a #CamelNameValueArray
+ * @index: an index
+ *
+ * Returns the value of the element at index @index.
+ *
+ * Returns: (transfer-none): (nullable): Value of the element at the given @index,
+ *    or %NULL on error.
+ *
+ * See: camel_name_value_array_get, camel_name_value_array_get_name
+ *
+ * Since: 3.24
+ **/
+const gchar *
+camel_name_value_array_get_value (const CamelNameValueArray *array,
+                                 guint index)
+{
+       const gchar *value = NULL;
+
+       g_return_val_if_fail (array != NULL, NULL);
+       g_return_val_if_fail (index < camel_name_value_array_length (array), NULL);
+
+       if (!camel_name_value_array_get (array, index, NULL, &value))
+               return NULL;
+
+       return value;
+}
+
+/**
+ * camel_name_value_array_append:
+ * @array: a #CamelNameValueArray
+ * @name: a name
+ * @value: a value
+ *
+ * Appends a new element of the name @name and the value @value
+ * at the end of @array.
+ *
+ * See: camel_name_value_array_set_named
+ *
+ * Since: 3.24
+ **/
+void
+camel_name_value_array_append (CamelNameValueArray *array,
+                              const gchar *name,
+                              const gchar *value)
+{
+       GArray *arr = (GArray *) array;
+       CamelNameValuePair pair;
+
+       g_return_if_fail (array != NULL);
+       g_return_if_fail (name != NULL);
+       g_return_if_fail (value != NULL);
+
+       pair.name = g_strdup (name);
+       pair.value = g_strdup (value);
+
+       g_array_append_val (arr, pair);
+}
+
+static gboolean
+camel_name_value_array_set_internal (CamelNameValueArray *array,
+                                    guint index,
+                                    const gchar *name,
+                                    const gchar *value)
+{
+       GArray *arr = (GArray *) array;
+       CamelNameValuePair *pair;
+       gboolean changed = FALSE;
+
+       g_return_val_if_fail (array != NULL, FALSE);
+       g_return_val_if_fail (index < camel_name_value_array_length (array), FALSE);
+
+       pair = &g_array_index (arr, CamelNameValuePair, index);
+
+       if (name && g_strcmp0 (pair->name, name) != 0) {
+               g_free (pair->name);
+               pair->name = g_strdup (name);
+               changed = TRUE;
+       }
+
+       if (value && g_strcmp0 (pair->value, value) != 0) {
+               g_free (pair->value);
+               pair->value = g_strdup (value);
+               changed = TRUE;
+       }
+
+       return changed;
+}
+
+/**
+ * camel_name_value_array_set:
+ * @array: a #CamelNameValueArray
+ * @index: an index
+ * @name: a name
+ * @value: a value
+ *
+ * Sets both the @name and the @value of the element at index @index.
+ *
+ * Returns: Whether the @array changed.
+ *
+ * See: camel_name_value_array_append, camel_name_value_array_set_name, camel_name_value_array_set_value
+ *
+ * Since: 3.24
+ **/
+gboolean
+camel_name_value_array_set (CamelNameValueArray *array,
+                           guint index,
+                           const gchar *name,
+                           const gchar *value)
+{
+       g_return_val_if_fail (array != NULL, FALSE);
+       g_return_val_if_fail (index < camel_name_value_array_length (array), FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+       g_return_val_if_fail (value != NULL, FALSE);
+
+       return camel_name_value_array_set_internal (array, index, name, value);
+}
+
+/**
+ * camel_name_value_array_set_name:
+ * @array: a #CamelNameValueArray
+ * @index: an index
+ * @name: a name
+ *
+ * Sets the @name of the element at index @index.
+ *
+ * Returns: Whether the @array changed.
+ *
+ * See: camel_name_value_array_set, camel_name_value_array_set_value
+ *
+ * Since: 3.24
+ **/
+gboolean
+camel_name_value_array_set_name (CamelNameValueArray *array,
+                                guint index,
+                                const gchar *name)
+{
+       g_return_val_if_fail (array != NULL, FALSE);
+       g_return_val_if_fail (index < camel_name_value_array_length (array), FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       return camel_name_value_array_set_internal (array, index, name, NULL);
+}
+
+/**
+ * camel_name_value_array_set_value:
+ * @array: a #CamelNameValueArray
+ * @index: an index
+ * @value: a value
+ *
+ * Sets the @value of the element at index @index.
+ *
+ * Returns: Whether the @array changed.
+ *
+ * See: camel_name_value_array_set, camel_name_value_array_set_name
+ *
+ * Since: 3.24
+ **/
+gboolean
+camel_name_value_array_set_value (CamelNameValueArray *array,
+                                 guint index,
+                                 const gchar *value)
+{
+       g_return_val_if_fail (array != NULL, FALSE);
+       g_return_val_if_fail (index < camel_name_value_array_length (array), FALSE);
+       g_return_val_if_fail (value != NULL, FALSE);
+
+       return camel_name_value_array_set_internal (array, index, NULL, value);
+}
+
+/**
+ * camel_name_value_array_set_named:
+ * @array: a #CamelNameValueArray
+ * @case_sensitive: whether to compare names case sensitively
+ * @name: a name
+ * @value: a value
+ *
+ * Finds an element named @name and sets its value to @value, or appends
+ * a new element, in case no such named lement exists in the @array yet.
+ * In case there are more elements named with @name only the first
+ * occurrence is changed. The @case_sensitive determines whether compare
+ * names case sensitively (%TRUE) or insensitively (%FALSE).
+ *
+ * Returns: Whether the @array changed.
+ *
+ * See: camel_name_value_array_append, camel_name_value_array_set
+ *
+ * Since: 3.24
+ **/
+gboolean
+camel_name_value_array_set_named (CamelNameValueArray *array,
+                                 gboolean case_sensitive,
+                                 const gchar *name,
+                                 const gchar *value)
+{
+       gboolean changed = FALSE;
+       guint index;
+
+       g_return_val_if_fail (array != NULL, FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+       g_return_val_if_fail (value != NULL, FALSE);
+
+       index = camel_name_value_array_find_named (array, case_sensitive, name);
+       if (index == (guint) -1) {
+               camel_name_value_array_append (array, name, value);
+               changed = TRUE;
+       } else {
+               changed = camel_name_value_array_set_value (array, index, value);
+       }
+
+       return changed;
+}
+
+/**
+ * camel_name_value_array_remove:
+ * @array: a #CamelNameValueArray
+ * @index: an index to remove
+ *
+ * Removes element at index @index.
+ *
+ * Returns: Whether the element was removed.
+ *
+ * Since: 3.24
+ **/
+gboolean
+camel_name_value_array_remove (CamelNameValueArray *array,
+                              guint index)
+{
+       g_return_val_if_fail (array != NULL, FALSE);
+       g_return_val_if_fail (index < camel_name_value_array_length (array), FALSE);
+
+       g_array_remove_index ((GArray *) array, index);
+
+       return TRUE;
+}
+
+/**
+ * camel_name_value_array_remove_named:
+ * @array: a #CamelNameValueArray
+ * @case_sensitive: whether to compare names case sensitively
+ * @name: a name to remove
+ * @all_occurrences: whether to remove all occurrences of the @name
+ *
+ * Removes elements of the @array with the given @name. The @case_sensitive
+ * determines whether compare case sensitively (%TRUE) or insensitively (%FALSE).
+ * If the @all_occurrences is set to %TRUE, then every elements with the @name
+ * are removed, otherwise only the first occurrence is removed.
+ *
+ * Returns: How many elements had been removed.
+ *
+ * Since: 3.24
+ **/
+guint
+camel_name_value_array_remove_named (CamelNameValueArray *array,
+                                    gboolean case_sensitive,
+                                    const gchar *name,
+                                    gboolean all_occurrences)
+{
+       guint index, removed = 0;
+
+       g_return_val_if_fail (array != NULL, 0);
+       g_return_val_if_fail (name != NULL, 0);
+
+       while (index = camel_name_value_array_find_named (array, case_sensitive, name), index != (guint) -1) {
+               if (!camel_name_value_array_remove (array, index))
+                       break;
+
+               removed++;
+
+               if (!all_occurrences)
+                       break;
+       }
+
+       return removed;
+}
+
+/**
+ * camel_name_value_array_clear:
+ * @array: a #CamelNameValueArray
+ *
+ * Removes all elements of the @array.
+ *
+ * Since: 3.24
+ **/
+void
+camel_name_value_array_clear (CamelNameValueArray *array)
+{
+       GArray *arr = (GArray *) array;
+
+       g_return_if_fail (array != NULL);
+
+       g_array_remove_range (arr, 0, arr->len);
+}
+
+/* ------------------------------------------------------------------------ */
+
+G_DEFINE_BOXED_TYPE (CamelNamedFlags,
+               camel_named_flags,
+               camel_named_flags_copy,
+               camel_named_flags_free)
+
+/**
+ * camel_named_flags_new:
+ *
+ * Creates a new #CamelNamedFlags.
+ *
+ * Returns: (transfer-full): A newly allocated #CamelNamedFlags.
+ *    Free it with camel_named_flags_free() when done with it.
+ *
+ * Since: 3.24
+ **/
+CamelNamedFlags *
+camel_named_flags_new (void)
+{
+       return (CamelNamedFlags *) g_ptr_array_new_with_free_func (g_free);
+}
+
+/**
+ * camel_named_flags_new_sized:
+ * @reserve_size: an array size to reserve
+ *
+ * Created a new #CamelNamedFlags, which has reserved @reserve_size
+ * elements. This value doesn't influence the camel_named_flags_length(),
+ * which returns zero on the array returned from this function.
+ *
+ * Returns: (transfer-full): A newly allocated #CamelNameValueArray.
+ *    Free it with camel_named_flags_free() when done with it.
+ *
+ * See: camel_name_value_array_new, camel_name_value_array_copy
+ *
+ * Since: 3.24
+ **/
+CamelNamedFlags *
+camel_named_flags_new_sized (guint reserve_size)
+{
+       return (CamelNamedFlags *) g_ptr_array_new_full (reserve_size, g_free);
+}
+
+/**
+ * camel_named_flags_copy:
+ * @named_flags: a #CamelNamedFlags
+ *
+ * Creates a copy of the @named_flags and returns it.
+ *
+ * Returns: (transfer-full): A newly allocated #CamelNamedFlags.
+ *    Free it with camel_named_flags_free() when done with it.
+ *
+ * Since: 3.24
+ **/
+CamelNamedFlags *
+camel_named_flags_copy (const CamelNamedFlags *named_flags)
+{
+       const GPtrArray *src = (const GPtrArray *) named_flags;
+       GPtrArray *arr;
+       guint ii;
+
+       if (!src)
+               return NULL;
+
+       arr = (GPtrArray *) camel_named_flags_new_sized (src->len);
+       for (ii = 0; ii < src->len; ii++) {
+               const gchar *name = g_ptr_array_index (src, ii);
+
+               if (name && *name)
+                       g_ptr_array_add (arr, g_strdup (name));
+       }
+
+       return (CamelNamedFlags *) arr;
+}
+
+/**
+ * camel_named_flags_free:
+ * @named_flags: (nullable): a #CamelNamedFlags, or %NULL
+ *
+ * Frees memory associated iwth the @named_flags. Does nothing,
+ * if @named_flags is %NULL.
+ *
+ * Since: 3.24
+ **/
+void
+camel_named_flags_free (CamelNamedFlags *named_flags)
+{
+       if (named_flags)
+               g_ptr_array_unref ((GPtrArray *) named_flags);
+}
+
+static guint
+camel_named_flags_find (CamelNamedFlags *named_flags,
+                       const gchar *name)
+{
+       GPtrArray *arr = (GPtrArray *) named_flags;
+       guint ii;
+
+       g_return_val_if_fail (named_flags != NULL, (guint) -1);
+       g_return_val_if_fail (name != NULL, (guint) -1);
+
+       for (ii = 0; ii < arr->len; ii++) {
+               const gchar *nm = g_ptr_array_index (arr, ii);
+
+               if (g_strcmp0 (nm, name) == 0)
+                       return ii;
+       }
+
+       return (guint) -1;
+}
+
+/**
+ * camel_named_flags_insert:
+ * @named_flags: a #CamelNamedFlags
+ * @name: name of the flag
+ *
+ * Inserts a flag named @name into the @named_flags, if it is not included
+ * already (comparing case sensitively), or does nothing otherwise.
+ *
+ * Returns: %TRUE the flag named @name was inserted; %FALSE otherwise.
+ *
+ * Since: 3.24
+ **/
+gboolean
+camel_named_flags_insert (CamelNamedFlags *named_flags,
+                         const gchar *name)
+{
+       GPtrArray *arr = (GPtrArray *) named_flags;
+       guint index;
+
+       g_return_val_if_fail (named_flags != NULL, FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       index = camel_named_flags_find (named_flags, name);
+
+       /* already there */
+       if (index != (guint) -1)
+               return FALSE;
+
+       g_ptr_array_add (arr, g_strdup (name));
+
+       return TRUE;
+}
+
+/**
+ * camel_named_flags_remove:
+ * @named_flags: a #CamelNamedFlags
+ * @name: name of the flag
+ *
+ * Removes a flag named @name from the @named_flags.
+ *
+ * Returns: %TRUE when the @named_flags contained a flag named @name,
+ *    comparing case sensitively, and it was removed; %FALSE otherwise.
+ *
+ * Since: 3.24
+ **/
+gboolean
+camel_named_flags_remove (CamelNamedFlags *named_flags,
+                         const gchar *name)
+{
+       GPtrArray *arr = (GPtrArray *) named_flags;
+       guint index;
+
+       g_return_val_if_fail (named_flags != NULL, FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       index = camel_named_flags_find (named_flags, name);
+
+       /* not there */
+       if (index == (guint) -1)
+               return FALSE;
+
+       g_ptr_array_remove_index (arr, index);
+
+       return TRUE;
+}
+
+/**
+ * camel_named_flags_contains:
+ * @named_flags: a #CamelNamedFlags
+ * @name: name of the flag
+ *
+ * Returns: Whether the @named_flags contains a flag named @name,
+ *    comparing case sensitively.
+ *
+ * Since: 3.24
+ **/
+gboolean
+camel_named_flags_contains (CamelNamedFlags *named_flags,
+                           const gchar *name)
+{
+       g_return_val_if_fail (named_flags != NULL, FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       return camel_named_flags_find (named_flags, name) != (guint) -1;
+}
+
+/**
+ * camel_named_flags_clear:
+ * @named_flags: a #CamelNamedFlags
+ *
+ * Removes all the elements of the array.
+ *
+ * Since: 3.24
+ **/
+void
+camel_named_flags_clear (CamelNamedFlags *named_flags)
+{
+       GPtrArray *arr = (GPtrArray *) named_flags;
+
+       g_return_if_fail (named_flags != NULL);
+
+       if (arr->len)
+               g_ptr_array_remove_range (arr, 0, arr->len);
+}
+
+/**
+ * camel_named_flags_length:
+ * @named_flags: a #CamelNamedFlags
+ *
+ * Returns: Length of the array, aka how many named flags are stored there.
+ *
+ * Since: 3.24
+ **/
+guint
+camel_named_flags_length (const CamelNamedFlags *named_flags)
+{
+       const GPtrArray *arr = (const GPtrArray *) named_flags;
+
+       g_return_val_if_fail (named_flags != NULL, 0);
+
+       return arr->len;
+}
+
+/**
+ * camel_named_flags_get:
+ * @named_flags: a #CamelNamedFlags
+ * @index: an index of an element
+ *
+ * Returns: (transfer-none): (nullable): Name of the flag in at the given @index,
+ *   or %NULL on error.
+ *
+ * Since: 3.24
+ **/
+const gchar *
+camel_named_flags_get (const CamelNamedFlags *named_flags,
+                      guint index)
+{
+       const GPtrArray *arr = (const GPtrArray *) named_flags;
+
+       g_return_val_if_fail (named_flags != NULL, NULL);
+       g_return_val_if_fail (index < camel_named_flags_length (named_flags), NULL);
+
+       return g_ptr_array_index (arr, index);
+}
+
+/* ------------------------------------------------------------------------ */
+
 /**
  * camel_mktime_utc:
  * @tm: the #tm to convert to a calendar time representation
diff --git a/camel/camel-mime-utils.h b/camel/camel-mime-utils.h
index 2a51044..0cd2101 100644
--- a/camel/camel-mime-utils.h
+++ b/camel/camel-mime-utils.h
@@ -30,6 +30,8 @@
 #include <glib-object.h>
 #include <camel/camel-enums.h>
 
+G_BEGIN_DECLS
+
 /* maximum recommended size of a line from camel_header_fold() */
 #define CAMEL_FOLD_SIZE (77)
 /* maximum hard size of a line from camel_header_fold() */
@@ -43,7 +45,95 @@ typedef enum {
 
 #define CAMEL_UUDECODE_STATE_MASK   (CAMEL_UUDECODE_STATE_BEGIN | CAMEL_UUDECODE_STATE_END)
 
-G_BEGIN_DECLS
+/**
+ * CamelNameValueArray:
+ *
+ * Since: 3.24
+ **/
+struct _CamelNameValueArray;
+typedef struct _CamelNameValueArray CamelNameValueArray;
+
+#define CAMEL_TYPE_NAME_VALUE_ARRAY (camel_name_value_array_get_type ())
+
+GType           camel_name_value_array_get_type        (void) G_GNUC_CONST;
+CamelNameValueArray *
+               camel_name_value_array_new      (void);
+CamelNameValueArray *
+               camel_name_value_array_new_sized
+                                               (guint reserve_size);
+CamelNameValueArray *
+               camel_name_value_array_copy     (const CamelNameValueArray *array);
+void           camel_name_value_array_free     (CamelNameValueArray *array);
+guint          camel_name_value_array_length   (const CamelNameValueArray *array);
+gboolean       camel_name_value_array_get      (const CamelNameValueArray *array,
+                                                guint index,
+                                                const gchar **out_name,
+                                                const gchar **out_value);
+const gchar *  camel_name_value_array_get_named
+                                               (const CamelNameValueArray *array,
+                                                gboolean case_sensitive,
+                                                const gchar *name);
+const gchar *  camel_name_value_array_get_name (const CamelNameValueArray *array,
+                                                guint index);
+const gchar *  camel_name_value_array_get_value
+                                               (const CamelNameValueArray *array,
+                                                guint index);
+void           camel_name_value_array_append   (CamelNameValueArray *array,
+                                                const gchar *name,
+                                                const gchar *value);
+gboolean       camel_name_value_array_set      (CamelNameValueArray *array,
+                                                guint index,
+                                                const gchar *name,
+                                                const gchar *value);
+gboolean       camel_name_value_array_set_name (CamelNameValueArray *array,
+                                                guint index,
+                                                const gchar *name);
+gboolean       camel_name_value_array_set_value
+                                               (CamelNameValueArray *array,
+                                                guint index,
+                                                const gchar *value);
+gboolean       camel_name_value_array_set_named
+                                               (CamelNameValueArray *array,
+                                                gboolean case_sensitive,
+                                                const gchar *name,
+                                                const gchar *value);
+gboolean       camel_name_value_array_remove   (CamelNameValueArray *array,
+                                                guint index);
+guint          camel_name_value_array_remove_named
+                                               (CamelNameValueArray *array,
+                                                gboolean case_sensitive,
+                                                const gchar *name,
+                                                gboolean all_occurrences);
+void           camel_name_value_array_clear    (CamelNameValueArray *array);
+
+/**
+ * CamelNamedFlags:
+ *
+ * Since: 3.24
+ **/
+struct _CamelNamedFlags;
+typedef struct _CamelNamedFlags CamelNamedFlags;
+
+#define CAMEL_TYPE_NAMED_FLAGS (camel_named_flags_get_type ())
+
+GType           camel_named_flags_get_type     (void) G_GNUC_CONST;
+CamelNamedFlags *
+               camel_named_flags_new           (void);
+CamelNamedFlags *
+               camel_named_flags_new_sized     (guint reserve_size);
+CamelNamedFlags *
+               camel_named_flags_copy          (const CamelNamedFlags *named_flags);
+void           camel_named_flags_free          (CamelNamedFlags *named_flags);
+gboolean       camel_named_flags_insert        (CamelNamedFlags *named_flags,
+                                                const gchar *name);
+gboolean       camel_named_flags_remove        (CamelNamedFlags *named_flags,
+                                                const gchar *name);
+gboolean       camel_named_flags_contains      (CamelNamedFlags *named_flags,
+                                                const gchar *name);
+void           camel_named_flags_clear         (CamelNamedFlags *named_flags);
+guint          camel_named_flags_length        (const CamelNamedFlags *named_flags);
+const gchar *  camel_named_flags_get           (const CamelNamedFlags *named_flags,
+                                                guint index);
 
 typedef struct _camel_header_param {
        struct _camel_header_param *next;
diff --git a/camel/camel.h b/camel/camel.h
index 1c6eead..4e6f621 100644
--- a/camel/camel.h
+++ b/camel/camel.h
@@ -57,7 +57,7 @@
 #include <camel/camel-memchunk.h>
 #include <camel/camel-mempool.h>
 #include <camel/camel-message-info.h>
-#include <camel/camel-message-content-info.h>
+#include <camel/camel-message-info-base.h>
 #include <camel/camel-mime-filter.h>
 #include <camel/camel-mime-filter-basic.h>
 #include <camel/camel-mime-filter-bestenc.h>


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