/*
 * SPDX-FileCopyrightText: (C) 2016 Red Hat, Inc. (www.redhat.com)
 * SPDX-License-Identifier: LGPL-2.0-or-later
 */

#include "evolution-data-server-config.h"

#include <stdio.h>
#include <string.h>

#include "camel-db.h"
#include "camel-enumtypes.h"
#include "camel-folder.h"
#include "camel-folder-summary.h"
#include "camel-message-info-base.h"
#include "camel-string-utils.h"
#include "camel-weak-ref-group.h"

#include "camel-message-info.h"

struct _CamelMessageInfoPrivate {
	GRecMutex property_lock;

	CamelWeakRefGroup *summary_wrg;	/* CamelFolderSummary * */
	gboolean dirty;			/* whether requires save to local disk/summary */
	const gchar *uid;		/* allocated in the string pool */
	gboolean abort_notifications;
	gboolean thaw_notify_folder;
	gboolean thaw_notify_folder_with_counts;
	guint freeze_notifications;
	guint folder_flagged_stamp;
};

enum {
	PROP_0,
	PROP_SUMMARY,
	PROP_DIRTY,
	PROP_FOLDER_FLAGGED,
	PROP_FOLDER_FLAGGED_STAMP,
	PROP_ABORT_NOTIFICATIONS,
	PROP_UID,
	PROP_FLAGS,
	PROP_USER_FLAGS,
	PROP_USER_TAGS,
	PROP_SUBJECT,
	PROP_FROM,
	PROP_TO,
	PROP_CC,
	PROP_MLIST,
	PROP_SIZE,
	PROP_DATE_SENT,
	PROP_DATE_RECEIVED,
	PROP_MESSAGE_ID,
	PROP_REFERENCES,
	PROP_HEADERS,
	PROP_USER_HEADERS,
	PROP_PREVIEW,
	N_PROPS
};

static GParamSpec *properties[N_PROPS] = { NULL, };

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (CamelMessageInfo, camel_message_info, G_TYPE_OBJECT)

static CamelMessageInfo *
message_info_clone (const CamelMessageInfo *mi,
		    CamelFolderSummary *assign_summary)
{
	CamelMessageInfo *result;
	const gchar *uid;
	const GArray *references;
	const CamelNameValueArray *headers;

	g_return_val_if_fail (CAMEL_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. */
	camel_message_info_property_lock (mi);

	if (!assign_summary) {
		CamelFolderSummary *mi_summary;

		mi_summary = camel_message_info_ref_summary (mi);
		result = camel_message_info_new (mi_summary);
		g_clear_object (&mi_summary);
	} else {
		result = camel_message_info_new (assign_summary);
	}

	g_object_freeze_notify (G_OBJECT (result));
	camel_message_info_set_abort_notifications (result, TRUE);

	uid = camel_message_info_pooldup_uid (mi);
	camel_message_info_set_uid (result, uid);
	camel_pstring_free (uid);

	camel_message_info_take_user_flags (result, camel_message_info_dup_user_flags (mi));
	camel_message_info_take_user_tags (result, camel_message_info_dup_user_tags (mi));
	camel_message_info_set_subject (result, camel_message_info_get_subject (mi));
	camel_message_info_set_from (result, camel_message_info_get_from (mi));
	camel_message_info_set_to (result, camel_message_info_get_to (mi));
	camel_message_info_set_cc (result, camel_message_info_get_cc (mi));
	camel_message_info_set_mlist (result, camel_message_info_get_mlist (mi));
	camel_message_info_set_preview (result, camel_message_info_get_preview (mi));
	camel_message_info_set_size (result, camel_message_info_get_size (mi));
	camel_message_info_set_date_sent (result, camel_message_info_get_date_sent (mi));
	camel_message_info_set_date_received (result, camel_message_info_get_date_received (mi));
	camel_message_info_set_message_id (result, camel_message_info_get_message_id (mi));

	references = camel_message_info_get_references (mi);
	if (references && references->len) {
		GArray *copy;
		guint ii;

		copy = g_array_sized_new (FALSE, FALSE, sizeof (guint64), references->len);

		for (ii = 0; ii < references->len; ii++) {
			guint64 refr = g_array_index (references, guint64, ii);

			g_array_append_val (copy, refr);
		}

		camel_message_info_take_references (result, copy);
	}

	headers = camel_message_info_get_headers (mi);
	if (headers) {
		camel_message_info_take_headers (result,
			camel_name_value_array_copy (headers));
	}

	headers = camel_message_info_get_user_headers (mi);
	if (headers) {
		camel_message_info_take_user_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. */
	camel_message_info_set_flags (result, ~0, camel_message_info_get_flags (mi));

	camel_message_info_property_unlock (mi);

	/* Also ensure 'dirty' flag, thus it can be eventually saved. */
	camel_message_info_set_dirty (result, TRUE);

	camel_message_info_set_abort_notifications (result, FALSE);
	g_object_thaw_notify (G_OBJECT (result));

	return result;
}

/* private function */
gboolean
_camel_message_info_util_decode_part (const gchar *db_part,
				      guint64 *out_message_id,
				      GArray **out_references); /* guint64 */

gboolean
_camel_message_info_util_decode_part (const gchar *db_part,
				      guint64 *out_message_id,
				      GArray **out_references) /* guint64 */
{
	CamelSummaryMessageID message_id;
	gchar *part = (gchar *) db_part;
	gint ii, count;

	*out_message_id = 0;
	*out_references = NULL;

	if (!db_part || !*db_part)
		return FALSE;

	message_id.id.part.hi = camel_util_bdata_get_number (&part, 0);
	message_id.id.part.lo = camel_util_bdata_get_number (&part, 0);

	*out_message_id = message_id.id.id;

	count = camel_util_bdata_get_number (&part, 0);

	if (count > 0) {
		GArray *references = g_array_sized_new (FALSE, FALSE, sizeof (guint64), count);

		for (ii = 0; ii < count; ii++) {
			message_id.id.part.hi = camel_util_bdata_get_number (&part, 0);
			message_id.id.part.lo = camel_util_bdata_get_number (&part, 0);

			g_array_append_val (references, message_id.id.id);
		}

		*out_references = references;
	}

	return TRUE;
}

static gboolean
message_info_load (CamelMessageInfo *mi,
		   const CamelStoreDBMessageRecord *record,
		   /* const */ gchar **bdata_ptr)
{
	gint ii, count;
	gchar *part, *label;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);
	g_return_val_if_fail (record != NULL, FALSE);
	g_return_val_if_fail (bdata_ptr != NULL, FALSE);

	camel_message_info_set_uid (mi, record->uid);
	camel_message_info_set_flags (mi, ~0, record->flags);
	camel_message_info_set_size (mi, record->size);
	camel_message_info_set_date_sent (mi, record->dsent);
	camel_message_info_set_date_received (mi, record->dreceived);

	camel_message_info_set_subject (mi, record->subject);
	camel_message_info_set_from (mi, record->from);
	camel_message_info_set_to (mi, record->to);
	camel_message_info_set_cc (mi, record->cc);
	camel_message_info_set_mlist (mi, record->mlist);
	camel_message_info_set_preview (mi, record->preview);

	/* Extract Message id & References */
	part = record->part;
	if (part) {
		guint64 message_id = 0;
		GArray *references = NULL;

		if (_camel_message_info_util_decode_part (part, &message_id, &references)) {
			camel_message_info_set_message_id (mi, message_id);
			camel_message_info_take_references (mi, references);
		}
	}

	/* Extract User flags/labels */
	part = record->labels;
	if (part) {
		CamelNamedFlags *user_flags;

		user_flags = camel_named_flags_new ();

		label = part;
		for (ii = 0; part[ii]; ii++) {
			if (part[ii] == ' ') {
				part[ii] = 0;
				if (label && *label)
					camel_named_flags_insert (user_flags, label);
				label = &(part[ii + 1]);
				part[ii] = ' ';
			}
		}
		if (label && *label)
			camel_named_flags_insert (user_flags, label);

		if (camel_named_flags_get_length (user_flags) == 0) {
			camel_named_flags_free (user_flags);
			user_flags = NULL;
		}

		camel_message_info_take_user_flags (mi, user_flags);
	}

	/* Extract User tags */
	part = record->usertags;
	if (part) {
		CamelNameValueArray *user_tags;

		count = camel_util_bdata_get_number (&part, 0);

		user_tags = camel_name_value_array_new_sized (count);

		for (ii = 0; ii < count; ii++) {
			gchar *name, *value;

			name = camel_util_bdata_get_string (&part, NULL);
			value = camel_util_bdata_get_string (&part, NULL);

			if (name)
				camel_name_value_array_set_named (user_tags, CAMEL_COMPARE_CASE_SENSITIVE, name, value ? value : "");

			g_free (name);
			g_free (value);
		}

		if (camel_name_value_array_get_length (user_tags) == 0) {
			camel_name_value_array_free (user_tags);
			user_tags = NULL;
		}

		camel_message_info_take_user_tags (mi, user_tags);
	}

	/* Extract User headers */
	part = record->userheaders;
	if (part) {
		CamelNameValueArray *user_headers;

		count = camel_util_bdata_get_number (&part, 0);

		user_headers = camel_name_value_array_new_sized (count);

		for (ii = 0; ii < count; ii++) {
			gchar *name, *value;

			name = camel_util_bdata_get_string (&part, NULL);
			value = camel_util_bdata_get_string (&part, NULL);

			if (name)
				camel_name_value_array_set_named (user_headers, CAMEL_COMPARE_CASE_SENSITIVE, name, value ? value : "");

			g_free (name);
			g_free (value);
		}

		if (camel_name_value_array_get_length (user_headers) == 0) {
			camel_name_value_array_free (user_headers);
			user_headers = NULL;
		}

		camel_message_info_take_user_headers (mi, user_headers);
	}

	return TRUE;
}

static gboolean
message_info_save (const CamelMessageInfo *mi,
		   CamelStoreDBMessageRecord *record,
		   GString *bdata_str)
{
	GString *tmp;
	const CamelNameValueArray *headers;
	CamelSummaryMessageID message_id;
	const GArray *references;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);
	g_return_val_if_fail (record != NULL, FALSE);
	g_return_val_if_fail (bdata_str != NULL, FALSE);

	record->uid = camel_pstring_strdup (camel_message_info_get_uid (mi));
	record->flags = camel_message_info_get_flags (mi);
	record->dirty = (record->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0 ? 1 : 0;

	record->size = camel_message_info_get_size (mi);
	record->dsent = camel_message_info_get_date_sent (mi);
	record->dreceived = camel_message_info_get_date_received (mi);

	record->subject = camel_pstring_strdup (camel_message_info_get_subject (mi));
	record->from = camel_pstring_strdup (camel_message_info_get_from (mi));
	record->to = camel_pstring_strdup (camel_message_info_get_to (mi));
	record->cc = camel_pstring_strdup (camel_message_info_get_cc (mi));
	record->mlist = camel_pstring_strdup (camel_message_info_get_mlist (mi));
	record->preview = g_strdup (camel_message_info_get_preview (mi));

	tmp = g_string_new (NULL);
	message_id.id.id = camel_message_info_get_message_id (mi);
	g_string_append_printf (tmp, "%lu %lu ", (gulong) message_id.id.part.hi, (gulong) message_id.id.part.lo);
	references = camel_message_info_get_references (mi);
	if (references) {
		guint ii;

		g_string_append_printf (tmp, "%lu", (gulong) references->len);
		for (ii = 0; ii < references->len; ii++) {
			message_id.id.id = g_array_index (references, guint64, ii);

			g_string_append_printf (tmp, " %lu %lu", (gulong) message_id.id.part.hi, (gulong) message_id.id.part.lo);
		}
	} else {
		g_string_append_c (tmp, '0');
	}
	record->part = g_string_free (tmp, FALSE);

	record->labels = camel_message_info_encode_user_flags (mi);
	record->usertags = camel_message_info_encode_user_tags (mi);

	tmp = g_string_new (NULL);
	headers = camel_message_info_get_user_headers (mi);
	if (headers) {
		guint32 ii, count;

		count = camel_name_value_array_get_length (headers);
		g_string_append_printf (tmp, "%" G_GUINT32_FORMAT, count);

		for (ii = 0; ii < count; ii++) {
			const gchar *name = NULL, *value = NULL;

			if (camel_name_value_array_get (headers, ii, &name, &value) && name && value) {
				g_string_append_printf (tmp, " %" G_GUINT32_FORMAT "-%s %" G_GUINT32_FORMAT "-%s",
					(guint32) strlen (name), name,
					(guint32) strlen (value), value);
			}
		}
	} else {
		g_string_append_c (tmp, '0');
	}
	record->userheaders = g_string_free (tmp, FALSE);

	return TRUE;
}

static void
message_info_set_property (GObject *object,
			   guint property_id,
			   const GValue *value,
			   GParamSpec *pspec)
{
	CamelMessageInfo *mi = CAMEL_MESSAGE_INFO (object);

	switch (property_id) {
	case PROP_SUMMARY:
		camel_weak_ref_group_set (mi->priv->summary_wrg, g_value_get_object (value));
		return;

	case PROP_DIRTY:
		camel_message_info_set_dirty (mi, g_value_get_boolean (value));
		return;

	case PROP_FOLDER_FLAGGED:
		camel_message_info_set_folder_flagged (mi, g_value_get_boolean (value));
		return;

	case PROP_ABORT_NOTIFICATIONS:
		camel_message_info_set_abort_notifications (mi, g_value_get_boolean (value));
		return;

	case PROP_UID:
		camel_message_info_set_uid (mi, g_value_get_string (value));
		return;

	case PROP_FLAGS:
		camel_message_info_set_flags (mi, ~0, g_value_get_flags (value));
		return;

	case PROP_USER_FLAGS:
		camel_message_info_take_user_flags (mi, g_value_dup_boxed (value));
		return;

	case PROP_USER_TAGS:
		camel_message_info_take_user_tags (mi, g_value_dup_boxed (value));
		return;

	case PROP_SUBJECT:
		camel_message_info_set_subject (mi, g_value_get_string (value));
		return;

	case PROP_FROM:
		camel_message_info_set_from (mi, g_value_get_string (value));
		return;

	case PROP_TO:
		camel_message_info_set_to (mi, g_value_get_string (value));
		return;

	case PROP_CC:
		camel_message_info_set_cc (mi, g_value_get_string (value));
		return;

	case PROP_MLIST:
		camel_message_info_set_mlist (mi, g_value_get_string (value));
		return;

	case PROP_SIZE:
		camel_message_info_set_size (mi, g_value_get_uint (value));
		return;

	case PROP_DATE_SENT:
		camel_message_info_set_date_sent (mi, g_value_get_int64 (value));
		return;

	case PROP_DATE_RECEIVED:
		camel_message_info_set_date_received (mi, g_value_get_int64 (value));
		return;

	case PROP_MESSAGE_ID:
		camel_message_info_set_message_id (mi, g_value_get_uint64 (value));
		return;

	case PROP_REFERENCES:
		camel_message_info_take_references (mi, g_value_dup_boxed (value));
		return;

	case PROP_HEADERS:
		camel_message_info_take_headers (mi, g_value_dup_boxed (value));
		return;

	case PROP_USER_HEADERS:
		camel_message_info_take_user_headers (mi, g_value_dup_boxed (value));
		return;

	case PROP_PREVIEW:
		camel_message_info_set_preview (mi, g_value_get_string (value));
		return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
message_info_get_property (GObject *object,
			   guint property_id,
			   GValue *value,
			   GParamSpec *pspec)
{
	CamelMessageInfo *mi = CAMEL_MESSAGE_INFO (object);

	switch (property_id) {
	case PROP_SUMMARY:
		g_value_take_object (value, camel_message_info_ref_summary (mi));
		return;

	case PROP_DIRTY:
		g_value_set_boolean (value, camel_message_info_get_dirty (mi));
		return;

	case PROP_FOLDER_FLAGGED:
		g_value_set_boolean (value, camel_message_info_get_folder_flagged (mi));
		return;

	case PROP_FOLDER_FLAGGED_STAMP:
		g_value_set_uint (value, camel_message_info_get_folder_flagged_stamp (mi));
		return;

	case PROP_ABORT_NOTIFICATIONS:
		g_value_set_boolean (value, camel_message_info_get_abort_notifications (mi));
		return;

	case PROP_UID:
		g_value_set_string (value, camel_message_info_get_uid (mi));
		return;

	case PROP_FLAGS:
		g_value_set_flags (value, camel_message_info_get_flags (mi));
		return;

	case PROP_USER_FLAGS:
		g_value_take_boxed (value, camel_message_info_dup_user_flags (mi));
		return;

	case PROP_USER_TAGS:
		g_value_take_boxed (value, camel_message_info_dup_user_tags (mi));
		return;

	case PROP_SUBJECT:
		g_value_set_string (value, camel_message_info_get_subject (mi));
		return;

	case PROP_FROM:
		g_value_set_string (value, camel_message_info_get_from (mi));
		return;

	case PROP_TO:
		g_value_set_string (value, camel_message_info_get_to (mi));
		return;

	case PROP_CC:
		g_value_set_string (value, camel_message_info_get_cc (mi));
		return;

	case PROP_MLIST:
		g_value_set_string (value, camel_message_info_get_mlist (mi));
		return;

	case PROP_SIZE:
		g_value_set_uint (value, camel_message_info_get_size (mi));
		return;

	case PROP_DATE_SENT:
		g_value_set_int64 (value, camel_message_info_get_date_sent (mi));
		return;

	case PROP_DATE_RECEIVED:
		g_value_set_int64 (value, camel_message_info_get_date_received (mi));
		return;

	case PROP_MESSAGE_ID:
		g_value_set_uint64 (value, camel_message_info_get_message_id (mi));
		return;

	case PROP_REFERENCES:
		g_value_take_boxed (value, camel_message_info_dup_references (mi));
		return;

	case PROP_HEADERS:
		g_value_take_boxed (value, camel_message_info_dup_headers (mi));
		return;

	case PROP_USER_HEADERS:
		g_value_take_boxed (value, camel_message_info_dup_user_headers (mi));
		return;

	case PROP_PREVIEW:
		g_value_take_string (value, camel_message_info_dup_preview (mi));
		return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
message_info_dispose (GObject *object)
{
	CamelMessageInfo *mi = CAMEL_MESSAGE_INFO (object);

	camel_weak_ref_group_set (mi->priv->summary_wrg, NULL);
	camel_pstring_free (mi->priv->uid);
	mi->priv->uid = NULL;

	/* Chain up to parent's method. */
	G_OBJECT_CLASS (camel_message_info_parent_class)->dispose (object);
}

static void
message_info_finalize (GObject *object)
{
	CamelMessageInfo *mi = CAMEL_MESSAGE_INFO (object);

	camel_weak_ref_group_unref (mi->priv->summary_wrg);
	g_rec_mutex_clear (&mi->priv->property_lock);

	/* Chain up to parent's method. */
	G_OBJECT_CLASS (camel_message_info_parent_class)->finalize (object);
}

static void
camel_message_info_class_init (CamelMessageInfoClass *class)
{
	GObjectClass *object_class;

	class->clone = message_info_clone;
	class->load = message_info_load;
	class->save = message_info_save;

	object_class = G_OBJECT_CLASS (class);
	object_class->set_property = message_info_set_property;
	object_class->get_property = message_info_get_property;
	object_class->dispose = message_info_dispose;
	object_class->finalize = message_info_finalize;

	/**
	 * CamelMessageInfo:summary:
	 *
	 * The #CamelFolderSummary to which the message info belongs, or %NULL.
	 * It can be set only during construction of the object.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_SUMMARY] =
		g_param_spec_object (
			"summary", NULL, NULL,
			CAMEL_TYPE_FOLDER_SUMMARY,
			G_PARAM_READWRITE |
			G_PARAM_CONSTRUCT_ONLY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:uid:
	 *
	 * A unique ID of the message in its folder.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_UID] =
		g_param_spec_string (
			"uid", NULL, NULL,
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:dirty:
	 *
	 * Flag, whether the info is changed and requires save to disk.
	 * Compare with CamelMessageInfo:folder-flagged
	 *
	 * Since: 3.24
	 **/
	properties[PROP_DIRTY] =
		g_param_spec_boolean (
			"dirty", NULL, NULL,
			FALSE,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo: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
	 **/
	properties[PROP_FOLDER_FLAGGED] =
		g_param_spec_boolean (
			"folder-flagged", NULL, NULL,
			FALSE,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:folder-flagged-stamp:
	 *
	 * The 'folder-flagged-stamp' is a stamp of the 'folder-flagged' flag. This stamp
	 * changes whenever anything would mark the @mi 'folder-flagged', regardless the @mi
	 * being already 'folder-flagged'. It can be used to recognize changes
	 * on the 'folder-flagged' flag during the time.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_FOLDER_FLAGGED_STAMP] =
		g_param_spec_uint (
			"folder-flagged-stamp", NULL, NULL,
			0, G_MAXUINT, 0,
			G_PARAM_READABLE |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:abort-notifications:
	 *
	 * Flag, whether the info is currently aborting notifications. It is used to avoid
	 * unnecessary 'folder-flagged' and 'dirty' flags changes and also to avoid
	 * associated folder's "changed" signal.
	 *f
	 * Since: 3.24
	 **/
	properties[PROP_ABORT_NOTIFICATIONS] =
		g_param_spec_boolean (
			"abort-notifications", NULL, NULL,
			FALSE,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:flags:
	 *
	 * Bit-or of #CamelMessageFlags.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_FLAGS] =
		g_param_spec_flags (
			"flags", NULL, NULL,
			CAMEL_TYPE_MESSAGE_FLAGS,
			0,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo: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
	 **/
	properties[PROP_USER_FLAGS] =
		g_param_spec_boxed (
			"user-flags", NULL, NULL,
			CAMEL_TYPE_NAMED_FLAGS,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo: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
	 **/
	properties[PROP_USER_TAGS] =
		g_param_spec_boxed (
			"user-tags", NULL, NULL,
			CAMEL_TYPE_NAME_VALUE_ARRAY,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:subject:
	 *
	 * Subject of the associated message.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_SUBJECT] =
		g_param_spec_string (
			"subject", NULL, NULL,
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:from:
	 *
	 * From address of the associated message.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_FROM] =
		g_param_spec_string (
			"from", NULL, NULL,
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:to:
	 *
	 * To address of the associated message.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_TO] =
		g_param_spec_string (
			"to", NULL, NULL,
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:cc:
	 *
	 * CC address of the associated message.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_CC] =
		g_param_spec_string (
			"cc", NULL, NULL,
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:mlist:
	 *
	 * Mailing list address of the associated message.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_MLIST] =
		g_param_spec_string (
			"mlist", NULL, NULL,
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:size:
	 *
	 * Size of the associated message.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_SIZE] =
		g_param_spec_uint (
			"size", NULL, NULL,
			0, G_MAXUINT32, 0,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:date-sent:
	 *
	 * Sent Date of the associated message.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_DATE_SENT] =
		g_param_spec_int64 (
			"date-sent", NULL, NULL,
			G_MININT64, G_MAXINT64, 0,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:date-received:
	 *
	 * Received date of the associated message.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_DATE_RECEIVED] =
		g_param_spec_int64 (
			"date-received", NULL, NULL,
			G_MININT64, G_MAXINT64, 0,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo: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
	 **/
	properties[PROP_MESSAGE_ID] =
		g_param_spec_uint64 (
			"message-id", NULL, NULL,
			0, G_MAXUINT64, 0,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo: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
	 **/
	properties[PROP_REFERENCES] =
		g_param_spec_boxed (
			"references", NULL, NULL,
			G_TYPE_ARRAY,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:headers:
	 *
	 * Headers of the associated message. Can be %NULL.
	 *
	 * Since: 3.24
	 **/
	properties[PROP_HEADERS] =
		g_param_spec_boxed (
			"headers", NULL, NULL,
			CAMEL_TYPE_NAME_VALUE_ARRAY,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:user-headers:
	 *
	 * User-defined headers of the associated message. Can be %NULL.
	 *
	 * Since: 3.42
	 **/
	properties[PROP_USER_HEADERS] =
		g_param_spec_boxed (
			"user-headers", NULL, NULL,
			CAMEL_TYPE_NAME_VALUE_ARRAY,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	/**
	 * CamelMessageInfo:preview:
	 *
	 * Body preview of the associated message. Can be %NULL.
	 *
	 * Since: 3.42
	 **/
	properties[PROP_PREVIEW] =
		g_param_spec_string (
			"preview", NULL, NULL,
			NULL,
			G_PARAM_READWRITE |
			G_PARAM_EXPLICIT_NOTIFY |
			G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties (object_class, N_PROPS, properties);
}

static void
camel_message_info_init (CamelMessageInfo *mi)
{
	mi->priv = camel_message_info_get_instance_private (mi);
	mi->priv->summary_wrg = camel_weak_ref_group_new ();

	g_rec_mutex_init (&mi->priv->property_lock);
}

/**
 * camel_message_info_new:
 * @summary: (nullable) (type CamelFolderSummary): parent #CamelFolderSummary object, or %NULL
 *
 * Create a new #CamelMessageInfo object, optionally for given @summary.
 *
 * Returns: (transfer full): a new #CamelMessageInfo object
 *
 * Since: 3.24
 **/
CamelMessageInfo *
camel_message_info_new (CamelFolderSummary *summary)
{
	GType type = CAMEL_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);
}

/**
 * camel_message_info_clone:
 * @mi: a #CamelMessageInfo to clone
 * @assign_summary: (nullable) (type CamelFolderSummary): 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 same
 * summary as the one with @mi is used.
 *
 * Returns: (transfer full): a new #CamelMessageInfo object, clone of the @mi
 *
 * Since: 3.24
 **/
CamelMessageInfo *
camel_message_info_clone (const CamelMessageInfo *mi,
			  CamelFolderSummary *assign_summary)
{
	CamelMessageInfoClass *klass;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);
	if (assign_summary)
		g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (assign_summary), NULL);

	klass = CAMEL_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);
}

/**
 * camel_message_info_load:
 * @mi: a #CamelMessageInfo to load
 * @record: a #CamelStoreDBMessageRecord to load the @mi from
 * @bdata_ptr: a backend specific data (bdata) pointer
 *
 * Load content of @mi from the data stored in @record. The @bdata_ptr points
 * to the current position of the record->bdata, where the read can continue.
 * Use helper functions camel_util_bdata_get_number() and camel_util_bdata_get_string()
 * to read data from it and also move forward the *bdata_ptr.
 *
 * After successful load of the @mi, the 'dirty' flag is unset.
 *
 * Returns: Whether the load was successful.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_load (CamelMessageInfo *mi,
			 const CamelStoreDBMessageRecord *record,
			 /* const */ gchar **bdata_ptr)
{
	CamelMessageInfoClass *klass;
	gboolean success;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);
	g_return_val_if_fail (record != NULL, FALSE);
	g_return_val_if_fail (bdata_ptr != NULL, FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->load != NULL, FALSE);

	g_object_freeze_notify (G_OBJECT (mi));
	camel_message_info_property_lock (mi);
	camel_message_info_set_abort_notifications (mi, TRUE);

	success = klass->load (mi, record, bdata_ptr);

	if (success)
		camel_message_info_set_dirty (mi, FALSE);

	camel_message_info_set_abort_notifications (mi, FALSE);
	camel_message_info_property_unlock (mi);
	g_object_thaw_notify (G_OBJECT (mi));

	return success;
}

/**
 * camel_message_info_save:
 * @mi: a #CamelMessageInfo
 * @record: a #CamelStoreDBMessageRecord to populate
 * @bdata_str: a #GString with a string to save as backend specific data (bdata)
 *
 * Save the @mi content to the message info record @record. It can populate all
 * but the record->bdata value, which is set fro mthe @bdata_str. Use helper functions
 * camel_util_bdata_put_number() and camel_util_bdata_put_string() to put data into the @bdata_str.
 *
 * Returns: Whether the save succeeded.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_save (const CamelMessageInfo *mi,
			 CamelStoreDBMessageRecord *record,
			 GString *bdata_str)
{
	CamelMessageInfoClass *klass;
	gboolean success;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);
	g_return_val_if_fail (record != NULL, FALSE);
	g_return_val_if_fail (bdata_str != NULL, FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->save != NULL, FALSE);

	camel_message_info_property_lock (mi);

	success = klass->save (mi, record, bdata_str);

	camel_message_info_property_unlock (mi);

	return success;
}

/**
 * camel_message_info_ref_summary:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer full) (nullable): Referenced #CamelFolderSummary to which
 * the @mi belongs, or %NULL, if there is none. Use g_object_unref() for
 * non-NULL returned values when done with it.
 *
 * Since: 3.24
 **/
CamelFolderSummary *
camel_message_info_ref_summary (const CamelMessageInfo *mi)
{
	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	return camel_weak_ref_group_get (mi->priv->summary_wrg);
}

/**
 * camel_message_info_property_lock:
 * @mi: a #CamelMessageInfo
 *
 * Acquires a property lock, which is used to ensure thread safety
 * when properties are changing. Release the lock with
 * camel_message_info_property_unlock().
 *
 * Note: Make sure the CamelFolderSummary lock is held before this lock,
 * if there will be called any 'set' function on the @mi, to avoid deadlock
 * when the summary would be set as dirty while another thread might try
 * to read values from the @mi, waiting for the property lock and holding
 * the summary lock at the same time.
 *
 * Since: 3.24
 **/
void
camel_message_info_property_lock (const CamelMessageInfo *mi)
{
	g_return_if_fail (CAMEL_IS_MESSAGE_INFO (mi));

	g_rec_mutex_lock (&mi->priv->property_lock);
}

/**
 * camel_message_info_property_unlock:
 * @mi: a #CamelMessageInfo
 *
 * Releases a property lock, previously acquired with
 * camel_message_info_property_lock().
 *
 * Since: 3.24
 **/
void
camel_message_info_property_unlock (const CamelMessageInfo *mi)
{
	g_return_if_fail (CAMEL_IS_MESSAGE_INFO (mi));

	g_rec_mutex_unlock (&mi->priv->property_lock);
}

static void
camel_message_info_update_summary_and_folder (CamelMessageInfo *mi,
					      gboolean update_counts)
{
	CamelFolderSummary *summary;

	g_return_if_fail (CAMEL_IS_MESSAGE_INFO (mi));

	camel_message_info_property_lock (mi);
	if (camel_message_info_get_notifications_frozen (mi)) {
		mi->priv->thaw_notify_folder = TRUE;
		mi->priv->thaw_notify_folder_with_counts |= update_counts;
		camel_message_info_property_unlock (mi);

		return;
	}
	camel_message_info_property_unlock (mi);

	summary = camel_message_info_ref_summary (mi);
	if (summary) {
		CamelFolder *folder;
		CamelMessageInfo *in_summary_mi = NULL;
		const gchar *uid;

		uid = camel_message_info_pooldup_uid (mi);

		/* This is for cases when a new message info had been created,
		   but not added into the summary yet. */
		if (uid && camel_folder_summary_check_uid (summary, uid) &&
		    (in_summary_mi = camel_folder_summary_peek_loaded (summary, uid)) == mi) {
			if (update_counts) {
				camel_folder_summary_lock (summary);
				g_object_freeze_notify (G_OBJECT (summary));

				camel_folder_summary_replace_flags (summary, camel_message_info_get_uid (mi), camel_message_info_get_flags (mi));

				g_object_thaw_notify (G_OBJECT (summary));
				camel_folder_summary_unlock (summary);
			}

			folder = camel_folder_summary_get_folder (summary);
			if (folder) {
				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);
			}
		}

		g_clear_object (&in_summary_mi);
		g_clear_object (&summary);
		camel_pstring_free (uid);
	}
}

/**
 * camel_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
camel_message_info_get_dirty (const CamelMessageInfo *mi)
{
	gboolean result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	camel_message_info_property_lock (mi);
	result = mi->priv->dirty;
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_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
camel_message_info_set_dirty (CamelMessageInfo *mi,
			      gboolean dirty)
{
	gboolean changed, abort_notifications;

	g_return_if_fail (CAMEL_IS_MESSAGE_INFO (mi));

	camel_message_info_property_lock (mi);

	changed = (!mi->priv->dirty) != (!dirty);
	if (changed)
		mi->priv->dirty = dirty;
	abort_notifications = mi->priv->abort_notifications;

	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_DIRTY]);

		if (dirty) {
			CamelFolderSummary *summary;

			summary = camel_message_info_ref_summary (mi);
			if (summary)
				camel_folder_summary_touch (summary);

			g_clear_object (&summary);
		}
	}
}

/**
 * camel_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
camel_message_info_get_folder_flagged (const CamelMessageInfo *mi)
{
	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	return (camel_message_info_get_flags (mi) & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0;
}

/**
 * camel_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
 * camel_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
camel_message_info_set_folder_flagged (CamelMessageInfo *mi,
				       gboolean folder_flagged)
{
	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	/* g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_FOLDER_FLAGGED]);
	   is called as part of the set_flags function */

	return camel_message_info_set_flags (mi, CAMEL_MESSAGE_FOLDER_FLAGGED,
		folder_flagged ? CAMEL_MESSAGE_FOLDER_FLAGGED : 0);
}

/**
 * camel_message_info_get_folder_flagged_stamp:
 * @mi: a #CamelMessageInfo
 *
 * The 'folder-flagged-stamp' is a stamp of the 'folder-flagged' flag. This stamp
 * changes whenever anything would mark the @mi as 'folder-flagged', regardless
 * the @mi being already 'folder-flagged'. It can be used to recognize changes
 * on the 'folder-flagged' flag during the time.
 *
 * Returns: Stamp of the 'folder-flagged' flag.
 *
 * Since: 3.24
 **/
guint
camel_message_info_get_folder_flagged_stamp (const CamelMessageInfo *mi)
{
	guint result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), ~0);

	camel_message_info_property_lock (mi);
	result = mi->priv->folder_flagged_stamp;
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_get_abort_notifications:
 * @mi: a #CamelMessageInfo
 *
 * Returns: Whether the @mi is aborting notifications, which means
 *   that it will not influence 'dirty' and 'folder-flagged' flags
 *   in the set/take functions, neither it will emit any GObject::notify
 *   signals on change, nor associated folder's "changed" signal.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_get_abort_notifications (const CamelMessageInfo *mi)
{
	gboolean result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	camel_message_info_property_lock (mi);
	result = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_abort_notifications:
 * @mi: a #CamelMessageInfo
 * @abort_notifications: a state to set
 *
 * Marks the @mi to abort any notifications, which means that it
 * will not influence 'dirty' and 'folder-flagged' flags in
 * the set/take functions, neither it will emit any GObject::notify
 * signals on change, nor associated folder's "changed" signal.
 *
 * Since: 3.24
 **/
void
camel_message_info_set_abort_notifications (CamelMessageInfo *mi,
					    gboolean abort_notifications)
{
	gboolean changed;
	g_return_if_fail (CAMEL_IS_MESSAGE_INFO (mi));

	camel_message_info_property_lock (mi);
	changed = (!mi->priv->abort_notifications) != (!abort_notifications);
	if (changed)
		mi->priv->abort_notifications = abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed)
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_ABORT_NOTIFICATIONS]);
}

/**
 * camel_message_info_freeze_notifications:
 * @mi: a #CamelMessageInfo
 *
 * Freezes all the notifications until the camel_message_info_thaw_notifications() is called.
 * This function can be called multiple times, where the last thaw will do the notifications.
 *
 * Since: 3.24
 **/
void
camel_message_info_freeze_notifications (CamelMessageInfo *mi)
{
	g_return_if_fail (CAMEL_IS_MESSAGE_INFO (mi));

	camel_message_info_property_lock (mi);
	mi->priv->freeze_notifications++;
	if (mi->priv->freeze_notifications == 1) {
		mi->priv->thaw_notify_folder = FALSE;
		mi->priv->thaw_notify_folder_with_counts = FALSE;
		g_object_freeze_notify (G_OBJECT (mi));
	}
	camel_message_info_property_unlock (mi);
}

/**
 * camel_message_info_thaw_notifications:
 * @mi: a #CamelMessageInfo
 *
 * Reverses the call of the camel_message_info_freeze_notifications().
 * If this is the last freeze, then the associated folder is also notified
 * about the change, if any happened during the freeze.
 *
 * Since: 3.24
 **/
void
camel_message_info_thaw_notifications (CamelMessageInfo *mi)
{
	g_return_if_fail (CAMEL_IS_MESSAGE_INFO (mi));

	camel_message_info_property_lock (mi);
	if (!mi->priv->freeze_notifications) {
		camel_message_info_property_unlock (mi);

		g_warn_if_reached ();
		return;
	}

	mi->priv->freeze_notifications--;
	if (!mi->priv->freeze_notifications) {
		gboolean notify_folder, notify_folder_with_counts;

		notify_folder = mi->priv->thaw_notify_folder;
		notify_folder_with_counts = mi->priv->thaw_notify_folder_with_counts;

		camel_message_info_property_unlock (mi);

		g_object_thaw_notify (G_OBJECT (mi));

		if (notify_folder)
			camel_message_info_update_summary_and_folder (mi, notify_folder_with_counts);
	} else {
		camel_message_info_property_unlock (mi);
	}
}

/**
 * camel_message_info_get_notifications_frozen:
 * @mi: a #CamelMessageInfo
 *
 * Returns: Whether the notifications are frozen.
 *
 * See: camel_message_info_freeze_notifications()
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_get_notifications_frozen (const CamelMessageInfo *mi)
{
	gboolean result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	camel_message_info_property_lock (mi);
	result = mi->priv->freeze_notifications > 0;
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_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 *
camel_message_info_get_uid (const CamelMessageInfo *mi)
{
	const gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	camel_message_info_property_lock (mi);
	result = mi->priv->uid;
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_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 *
camel_message_info_pooldup_uid (const CamelMessageInfo *mi)
{
	const gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	camel_message_info_property_lock (mi);
	result = camel_pstring_strdup (mi->priv->uid);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_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, unless the @mi is aborting notifications. This change
 * does not influence the 'folder-flagged' flag.
 *
 * Returns: Whether the UID changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_uid (CamelMessageInfo *mi,
			    const gchar *uid)
{
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	camel_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);
	}
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_UID]);
		camel_message_info_set_dirty (mi, TRUE);
	}

	return changed;
}

/**
 * camel_message_info_get_flags:
 * @mi: a #CamelMessageInfo
 *
 * Returns: Bit-or of #CamelMessageFlags set on the @mi.
 *
 * Since: 3.24
 **/
guint32
camel_message_info_get_flags (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	guint32 result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), 0);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, 0);
	g_return_val_if_fail (klass->get_flags != NULL, 0);

	camel_message_info_property_lock (mi);
	result = klass->get_flags (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_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. Both @mask and @set are bit-or
 * of #CamelMessageFlags.
 *
 * If the @mi changed, the 'dirty' flag and the 'folder-flagged' flag are
 * set automatically, unless the @mi is aborting notifications. There is also emitted
 * folder's "changed" signal for this @mi, if necessary. In case
 * the CAMEL_MESSAGE_FOLDER_FLAGGED flag would be set and the @mi is
 * not aborting notifications, the 'folder-flagged-stamp' changes too.
 *
 * Returns: Whether the flags changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_flags (CamelMessageInfo *mi,
			      guint32 mask,
			      guint32 set)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_flags != NULL, FALSE);

	camel_message_info_property_lock (mi);

	changed = klass->set_flags (mi, mask, set);
	abort_notifications = mi->priv->abort_notifications;

	if (!abort_notifications &&
	    (mask & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0 &&
	    (set & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0)
		mi->priv->folder_flagged_stamp++;

	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_FLAGS]);
		camel_message_info_set_dirty (mi, TRUE);

		/* Only if the folder-flagged was not part of the change */
		if (!(mask & CAMEL_MESSAGE_FOLDER_FLAGGED))
			camel_message_info_set_folder_flagged (mi, TRUE);
		else
			g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_FOLDER_FLAGGED]);

		camel_message_info_update_summary_and_folder (mi, TRUE);
	}

	return changed;
}

/**
 * camel_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
camel_message_info_get_user_flag (const CamelMessageInfo *mi,
				  const gchar *name)
{
	CamelMessageInfoClass *klass;
	gboolean result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->get_user_flag != NULL, FALSE);

	camel_message_info_property_lock (mi);
	result = klass->get_user_flag (mi, name);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_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, unless the @mi is aborting notifications. There is also emitted
 * folder's "changed" signal for this @mi, if necessary.
 *
 * Returns: Whether the message info changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_user_flag (CamelMessageInfo *mi,
				  const gchar *name,
				  gboolean state)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);
	g_return_val_if_fail (name != NULL, FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_user_flag != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_user_flag (mi, name, state);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_USER_FLAGS]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);

		camel_message_info_update_summary_and_folder (mi, FALSE);
	}

	return changed;
}

/**
 * camel_message_info_get_user_flags:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer none) (nullable): A #CamelNamedFlags with all the currently set
 *   user flags on the @mi. Do not modify it.
 *
 * Since: 3.24
 **/
const CamelNamedFlags *
camel_message_info_get_user_flags (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	const CamelNamedFlags *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_user_flags != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_user_flags (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_dup_user_flags:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer full) (nullable): 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 *
camel_message_info_dup_user_flags (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	CamelNamedFlags *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->dup_user_flags != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->dup_user_flags (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_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, unless the @mi is aborting notifications. There is also emitted
 * folder's "changed" signal for this @mi, if necessary.
 *
 * Note that it's not safe to use the @user_flags after the call to this function,
 * because it can be freed due to no change.
 *
 * Returns: Whether the message info changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_take_user_flags (CamelMessageInfo *mi,
				    CamelNamedFlags *user_flags)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->take_user_flags != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->take_user_flags (mi, user_flags);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_USER_FLAGS]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);

		camel_message_info_update_summary_and_folder (mi, FALSE);
	}

	return changed;
}

/**
 * camel_message_info_encode_user_flags:
 * @mi: a #CamelMessageInfo
 *
 * Encodes the user flags into a string. Free the returned
 * string with g_free(), when no longer needed.
 *
 * Returns: (transfer full): a newly allocated string with the user flags
 *
 * Since: 3.58
 **/
gchar *
camel_message_info_encode_user_flags (const CamelMessageInfo *mi)
{
	const CamelNamedFlags *user_flags;
	GString *tmp;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	camel_message_info_property_lock (mi);

	tmp = g_string_new (NULL);
	user_flags = camel_message_info_get_user_flags (mi);
	if (user_flags) {
		guint ii, count;

		count = camel_named_flags_get_length (user_flags);
		for (ii = 0; ii < count; ii++) {
			const gchar *name = camel_named_flags_get (user_flags, ii);

			if (name && *name) {
				if (tmp->len)
					g_string_append_c (tmp, ' ');
				g_string_append (tmp, name);
			}
		}
	}

	camel_message_info_property_unlock (mi);

	return g_string_free (tmp, FALSE);
}

/**
 * camel_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 *
camel_message_info_get_user_tag (const CamelMessageInfo *mi,
				 const gchar *name)
{
	CamelMessageInfoClass *klass;
	const gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);
	g_return_val_if_fail (name != NULL, NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_user_tag != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_user_tag (mi, name);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_dup_user_tag:
 * @mi: a #CamelMessageInfo
 * @name: user tag name
 *
 * 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 *
camel_message_info_dup_user_tag (const CamelMessageInfo *mi,
				 const gchar *name)
{
	gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);
	g_return_val_if_fail (name != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = g_strdup (camel_message_info_get_user_tag (mi, name));
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_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, unless the @mi is aborting notifications. There is also emitted
 * folder's "changed" signal for this @mi, if necessary.
 *
 * Returns: Whether the @mi changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_user_tag (CamelMessageInfo *mi,
				 const gchar *name,
				 const gchar *value)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);
	g_return_val_if_fail (name != NULL, FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_user_tag != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_user_tag (mi, name, value);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_USER_TAGS]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);

		camel_message_info_update_summary_and_folder (mi, FALSE);
	}

	return changed;
}

/**
 * camel_message_info_get_user_tags:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer none) (nullable): a #CamelNameValueArray containing all set
 *   user tags of the @mi. Do not modify it.
 *
 * Since: 3.24
 **/
const CamelNameValueArray *
camel_message_info_get_user_tags (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	const CamelNameValueArray *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_user_tags != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_user_tags (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_dup_user_tags:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer full) (nullable): 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 *
camel_message_info_dup_user_tags (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	CamelNameValueArray *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->dup_user_tags != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->dup_user_tags (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_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, unless the @mi is aborting notifications. There is also emitted
 * folder's "changed" signal for this @mi, if necessary.
 *
 * Note that it's not safe to use the @user_tags after the call to this function,
 * because it can be freed due to no change.
 *
 * Returns: Whether the @mi changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_take_user_tags (CamelMessageInfo *mi,
				   CamelNameValueArray *user_tags)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->take_user_tags != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->take_user_tags (mi, user_tags);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_USER_TAGS]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);

		camel_message_info_update_summary_and_folder (mi, FALSE);
	}

	return changed;
}

/**
 * camel_message_info_encode_user_tags:
 * @mi: a #CamelMessageInfo
 *
 * Encodes the user tags into a string. Free the returned
 * string with g_free(), when no longer needed.
 *
 * Returns: (transfer full): a newly allocated string with the user tags
 *
 * Since: 3.58
 **/
gchar *
camel_message_info_encode_user_tags (const CamelMessageInfo *mi)
{
	const CamelNameValueArray *user_tags;
	GString *tmp;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	tmp = g_string_new (NULL);

	camel_message_info_property_lock (mi);

	user_tags = camel_message_info_get_user_tags (mi);
	if (user_tags) {
		guint ii, count;

		count = camel_name_value_array_get_length (user_tags);
		g_string_append_printf (tmp, "%lu", (gulong) count);

		for (ii = 0; ii < count; ii++) {
			const gchar *name = NULL, *value = NULL;

			if (camel_name_value_array_get (user_tags, ii, &name, &value)) {
				if (!name)
					name = "";
				if (!value)
					value = "";

				g_string_append_printf (tmp, " %lu-%s %lu-%s", (gulong) strlen (name), name, (gulong) strlen (value), value);
			}
		}
	} else {
		g_string_append_c (tmp, '0');
	}

	camel_message_info_property_unlock (mi);

	return g_string_free (tmp, FALSE);
}

/**
 * camel_message_info_get_subject:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer none): Subject of the #mi.
 *
 * Since: 3.24
 **/
const gchar *
camel_message_info_get_subject (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	const gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_subject != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_subject (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_subject:
 * @mi: a #CamelMessageInfo
 * @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, unless the @mi is aborting notifications. There is not emitted
 * folder's "changed" signal for this @mi.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_subject (CamelMessageInfo *mi,
				const gchar *subject)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_subject != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_subject (mi, subject);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_SUBJECT]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);
	}

	return changed;
}

/**
 * camel_message_info_get_from:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer none): From address of the @mi.
 *
 * Since: 3.24
 **/
const gchar *
camel_message_info_get_from (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	const gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_from != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_from (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_from:
 * @mi: a #CamelMessageInfo
 * @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, unless the @mi is aborting notifications. There is not emitted
 * folder's "changed" signal for this @mi.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_from (CamelMessageInfo *mi,
			     const gchar *from)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_from != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_from (mi, from);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_FROM]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);
	}

	return changed;
}

/**
 * camel_message_info_get_to:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer none): To address of the @mi.
 *
 * Since: 3.24
 **/
const gchar *
camel_message_info_get_to (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	const gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_to != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_to (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_to:
 * @mi: a #CamelMessageInfo
 * @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, unless the @mi is aborting notifications. There is not emitted
 * folder's "changed" signal for this @mi.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_to (CamelMessageInfo *mi,
			   const gchar *to)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_to != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_to (mi, to);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_TO]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);
	}

	return changed;
}

/**
 * camel_message_info_get_cc:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer none): CC address of the @mi.
 *
 * Since: 3.24
 **/
const gchar *
camel_message_info_get_cc (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	const gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_cc != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_cc (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_cc:
 * @mi: a #CamelMessageInfo
 * @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, unless the @mi is aborting notifications. There is not emitted
 * folder's "changed" signal for this @mi.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_cc (CamelMessageInfo *mi,
			   const gchar *cc)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_cc != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_cc (mi, cc);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_CC]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);
	}

	return changed;
}

/**
 * camel_message_info_get_mlist:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer none): Mailing list address of the @mi.
 *
 * Since: 3.24
 **/
const gchar *
camel_message_info_get_mlist (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	const gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_mlist != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_mlist (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_mlist:
 * @mi: a #CamelMessageInfo
 * @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, unless the @mi is aborting notifications. There is not emitted
 * folder's "changed" signal for this @mi.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_mlist (CamelMessageInfo *mi,
			      const gchar *mlist)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_mlist != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_mlist (mi, mlist);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_MLIST]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);
	}

	return changed;
}

/**
 * camel_message_info_get_size:
 * @mi: a #CamelMessageInfo
 *
 * Returns: Size of the associated message.
 *
 * Since: 3.24
 **/
guint32
camel_message_info_get_size (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	guint32 result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), 0);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, 0);
	g_return_val_if_fail (klass->get_size != NULL, 0);

	camel_message_info_property_lock (mi);
	result = klass->get_size (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_size:
 * @mi: a #CamelMessageInfo
 * @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, unless the @mi is aborting notifications. There is not emitted
 * folder's "changed" signal for this @mi.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_size (CamelMessageInfo *mi,
			     guint32 size)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_size != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_size (mi, size);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_SIZE]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);
	}

	return changed;
}

/**
 * camel_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
camel_message_info_get_date_sent (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	gint64 result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), 0);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, 0);
	g_return_val_if_fail (klass->get_date_sent != NULL, 0);

	camel_message_info_property_lock (mi);
	result = klass->get_date_sent (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_date_sent:
 * @mi: a #CamelMessageInfo
 * @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, unless the @mi is aborting notifications. There is not emitted
 * folder's "changed" signal for this @mi.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_date_sent (CamelMessageInfo *mi,
				  gint64 date_sent)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_date_sent != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_date_sent (mi, date_sent);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_DATE_SENT]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);
	}

	return changed;
}

/**
 * camel_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
camel_message_info_get_date_received (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	gint64 result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), 0);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, 0);
	g_return_val_if_fail (klass->get_date_received != NULL, 0);

	camel_message_info_property_lock (mi);
	result = klass->get_date_received (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_date_received:
 * @mi: a #CamelMessageInfo
 * @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, unless the @mi is aborting notifications. There is not emitted
 * folder's "changed" signal for this @mi.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_date_received (CamelMessageInfo *mi,
				      gint64 date_received)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_date_received != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_date_received (mi, date_received);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_DATE_RECEIVED]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);
	}

	return changed;
}

/**
 * camel_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
camel_message_info_get_message_id (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	guint64 result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), 0);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, 0);
	g_return_val_if_fail (klass->get_message_id != NULL, 0);

	camel_message_info_property_lock (mi);
	result = klass->get_message_id (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_message_id:
 * @mi: a #CamelMessageInfo
 * @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, unless the @mi is aborting notifications. There is not emitted
 * folder's "changed" signal for this @mi.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_set_message_id (CamelMessageInfo *mi,
				   guint64 message_id)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_message_id != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_message_id (mi, message_id);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_MESSAGE_ID]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);
	}

	return changed;
}

/**
 * camel_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) (element-type guint64): A #GArray of
 *   guint64 encoded Message-ID-s; or %NULL when none are available.
 *
 * Since: 3.24
 **/
const GArray *
camel_message_info_get_references (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	const GArray *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_references != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_references (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_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) (element-type guint64): 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 *
camel_message_info_dup_references (const CamelMessageInfo *mi)
{
	const GArray *arr;
	GArray *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	camel_message_info_property_lock (mi);
	arr = camel_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;
	}
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_take_references:
 * @mi: a #CamelMessageInfo
 * @references: (element-type guint64) (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, unless the @mi is aborting notifications. There is not emitted
 * folder's "changed" signal for this @mi.
 *
 * Note that it's not safe to use the @references after the call to this function,
 * because it can be freed due to no change.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_take_references (CamelMessageInfo *mi,
				    GArray *references)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->take_references != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->take_references (mi, references);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_REFERENCES]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);
	}

	return changed;
}

/**
 * camel_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 *
camel_message_info_get_headers (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	const CamelNameValueArray *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_headers != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_headers (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_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 *
camel_message_info_dup_headers (const CamelMessageInfo *mi)
{
	const CamelNameValueArray *arr;
	CamelNameValueArray *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	camel_message_info_property_lock (mi);
	arr = camel_message_info_get_headers (mi);
	if (arr) {
		result = camel_name_value_array_copy (arr);
	} else {
		result = NULL;
	}
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_take_headers:
 * @mi: a #CamelMessageInfo
 * @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, unless the @mi is aborting notifications. There is not emitted
 * folder's "changed" signal for this @mi.
 *
 * Note that it's not safe to use the @headers after the call to this function,
 * because it can be freed due to no change.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.24
 **/
gboolean
camel_message_info_take_headers (CamelMessageInfo *mi,
				 CamelNameValueArray *headers)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->take_headers != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->take_headers (mi, headers);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_HEADERS]);
		camel_message_info_set_dirty (mi, TRUE);
		camel_message_info_set_folder_flagged (mi, TRUE);
	}

	return changed;
}

/**
 * camel_message_info_get_user_header:
 * @mi: a #CamelMessageInfo
 * @name: header name
 *
 * Returns: (transfer none) (nullable): Value of the header named @name from
 *    the user-defined message headers of the associated message, or %NULL,
 *    when not available.
 *
 * Since: 3.42
 **/
const gchar *
camel_message_info_get_user_header (const CamelMessageInfo *mi,
				    const gchar *name)
{
	CamelMessageInfoClass *klass;
	const gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);
	g_return_val_if_fail (name != NULL, NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_user_header != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_user_header (mi, name);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_dup_user_header:
 * @mi: a #CamelMessageInfo
 * @name: header name
 *
 * Returns: (transfer full) (nullable): Value of the header named @name from
 *    the user-defined message headers of the associated message, or %NULL,
 *    when not available. Free the returned string with g_free(), when no longer
 *    needed.
 *
 * Since: 3.42
 **/
gchar *
camel_message_info_dup_user_header (const CamelMessageInfo *mi,
				    const gchar *name)
{
	gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	camel_message_info_property_lock (mi);
	result = g_strdup (camel_message_info_get_user_header (mi, name));
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_user_header:
 * @mi: a #CamelMessageInfo
 * @name: header name
 * @value: (nullable): header value, or %NULL
 *
 * Set @value for a single user-defined message header of the associated message.
 * When the @value is %NULL, the header @name is removed from the user-defined
 * headers.
 *
 * If the @mi changed, the 'dirty' flag is set automatically, unless the @mi is
 * aborting notifications. There is not emitted folder's "changed" signal for this @mi.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.42
 **/
gboolean
camel_message_info_set_user_header (CamelMessageInfo *mi,
				    const gchar *name,
				    const gchar *value)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_user_header != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_user_header (mi, name, value);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_USER_HEADERS]);
		camel_message_info_set_dirty (mi, TRUE);
	}

	return changed;
}

/**
 * camel_message_info_get_user_headers:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer none) (nullable): All the user-defined message headers
 *    of the associated message, or %NULL, when none are available.
 *
 * Since: 3.42
 **/
const CamelNameValueArray *
camel_message_info_get_user_headers (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	const CamelNameValueArray *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_user_headers != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_user_headers (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_dup_user_headers:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer full) (nullable): All the user-defined 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.42
 **/
CamelNameValueArray *
camel_message_info_dup_user_headers (const CamelMessageInfo *mi)
{
	const CamelNameValueArray *arr;
	CamelNameValueArray *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	camel_message_info_property_lock (mi);
	arr = camel_message_info_get_user_headers (mi);
	if (arr) {
		result = camel_name_value_array_copy (arr);
	} else {
		result = NULL;
	}
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_take_user_headers:
 * @mi: a #CamelMessageInfo
 * @headers: (transfer full) (nullable): headers to set, as #CamelNameValueArray, or %NULL
 *
 * Takes user-defined message headers of the associated message.
 *
 * If the @mi changed, the 'dirty' flag is set automatically, unless the @mi is
 * aborting notifications. There is not emitted folder's "changed" signal for this @mi.
 *
 * Note that it's not safe to use the @headers after the call to this function,
 * because it can be freed due to no change.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.42
 **/
gboolean
camel_message_info_take_user_headers (CamelMessageInfo *mi,
				      CamelNameValueArray *headers)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->take_user_headers != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->take_user_headers (mi, headers);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_USER_HEADERS]);
		camel_message_info_set_dirty (mi, TRUE);
	}

	return changed;
}

/**
 * camel_message_info_get_preview:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer none) (nullable): Body preview of the associated
 *    message, or %NULL, when not available.
 *
 * Since: 3.42
 **/
const gchar *
camel_message_info_get_preview (const CamelMessageInfo *mi)
{
	CamelMessageInfoClass *klass;
	const gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, NULL);
	g_return_val_if_fail (klass->get_preview != NULL, NULL);

	camel_message_info_property_lock (mi);
	result = klass->get_preview (mi);
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_dup_preview:
 * @mi: a #CamelMessageInfo
 *
 * Returns: (transfer none) (nullable): Body preview of the associated
 *    message, or %NULL, when not available. Free the returned string
 *    with g_free(), when no longer needed.
 *
 * Since: 3.42
 **/
gchar *
camel_message_info_dup_preview (const CamelMessageInfo *mi)
{
	gchar *result;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), NULL);

	camel_message_info_property_lock (mi);
	result = g_strdup (camel_message_info_get_preview (mi));
	camel_message_info_property_unlock (mi);

	return result;
}

/**
 * camel_message_info_set_preview:
 * @mi: a #CamelMessageInfo
 * @preview: (nullable): message body preview, or %NULL
 *
 * Set @preview as the body preview of the associated message. Use %NULL or an empty
 * string to unset the value.
 *
 * If the @mi changed, the 'dirty' flag is set automatically, unless the @mi is
 * aborting notifications. There is not emitted folder's "changed" signal for this @mi.
 *
 * Returns: Whether the value changed.
 *
 * Since: 3.42
 **/
gboolean
camel_message_info_set_preview (CamelMessageInfo *mi,
				const gchar *preview)
{
	CamelMessageInfoClass *klass;
	gboolean changed, abort_notifications;

	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (mi), FALSE);

	klass = CAMEL_MESSAGE_INFO_GET_CLASS (mi);
	g_return_val_if_fail (klass != NULL, FALSE);
	g_return_val_if_fail (klass->set_preview != NULL, FALSE);

	camel_message_info_property_lock (mi);
	changed = klass->set_preview (mi, preview);
	abort_notifications = mi->priv->abort_notifications;
	camel_message_info_property_unlock (mi);

	if (changed && !abort_notifications) {
		g_object_notify_by_pspec (G_OBJECT (mi), properties[PROP_PREVIEW]);
		camel_message_info_set_dirty (mi, TRUE);
	}

	return changed;
}

/**
 * camel_message_info_dump:
 * @mi: a #CamelMessageInfo
 *
 * Dumps the mesasge info @mi to stdout. This is meand for debugging
 * purposes only.
 *
 * Since: 3.24
 **/
void
camel_message_info_dump (CamelMessageInfo *mi)
{
	if (!mi) {
		printf ("No message info\n");
		return;
	}

	camel_message_info_property_lock (mi);

	printf ("Message info %s:\n", G_OBJECT_TYPE_NAME (mi));
	printf ("   UID: %s\n", camel_message_info_get_uid (mi));
	printf ("   Flags: %04x\n", camel_message_info_get_flags (mi));
	printf ("   From: %s\n", camel_message_info_get_from (mi));
	printf ("   To: %s\n", camel_message_info_get_to (mi));
	printf ("   Cc: %s\n", camel_message_info_get_cc (mi));
	printf ("   Mailing list: %s\n", camel_message_info_get_mlist (mi));
	printf ("   Subject: %s\n", camel_message_info_get_subject (mi));

	camel_message_info_property_unlock (mi);
}
