# HG changeset patch
# Node ID 7483adcb7d9a05a553c808798a641b530cd88e31
# Parent  fac24c225f6ea4b6192ffcc6131e6868fb7c3a45
diff --git a/libpurple/data/im.pidgin.Purple.ConversationManager.gschema.xml b/libpurple/data/im.pidgin.Purple.ConversationManager.gschema.xml
new file mode 100644
--- /dev/null
+++ b/libpurple/data/im.pidgin.Purple.ConversationManager.gschema.xml
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="utf-8"?>
+<schemalist>
+  <enum id="im.pidgin.Purple.ConversationType">
+    <value nick="unset" value="0"/>
+    <value nick="dm" value="1"/>
+    <value nick="group dm" value="2"/>
+    <value nick="channel" value="3"/>
+    <value nick="thread" value="4"/>
+  </enum>
+
+  <schema id="im.pidgin.Purple.ConversationManager.Conversation">
+    <key name="account-id" type="s">
+      <default>""</default>
+      <summary>The id of the account of the conversation</summary>
+      <description>
+        The identifier of the account that this conversation belongs to.
+      </description>
+    </key>
+
+    <key name="age-restricted" type="b">
+      <default>false</default>
+      <summary>Whether or not the conversation is marked for adults</summary>
+      <description>
+        Conversations can be marked as age restricted to limit their exposure
+        to minors.
+      </description>
+    </key>
+
+    <key name="alias" type="s">
+      <default>""</default>
+      <summary>A user defined alias for the conversation</summary>
+      <description>
+        An alias client side only name that a user can give a conversation.
+      </description>
+    </key>
+
+    <key name="created-on" type="s">
+      <default>""</default>
+      <summary>The timestamp of when the conversation was created</summary>
+      <description>
+        An ISO-8601 formatted timestamp of time that the conversation was
+        created on the server.
+      </description>
+    </key>
+
+    <key name="creator-id" type="s">
+      <default>""</default>
+      <summary>The id of the user that created the conversation</summary>
+      <description>
+        Some protocols track who created a conversation. This field holds the
+        id of that user that will be resolved at run time.
+      </description>
+    </key>
+
+    <key name="description" type="s">
+      <default>""</default>
+      <summary>The description of the conversation</summary>
+      <description>
+        Some protocols allow a description for conversations to better describe
+        their purpose.
+      </description>
+    </key>
+
+    <key name="drafting" type="b">
+      <default>false</default>
+      <summary>Whether or not the user has input a message but has not sent it</summary>
+      <description>
+        This handles when a user has started drafting a message but has yet to
+        send it.
+      </description>
+    </key>
+
+    <key name="error" type="(sxs)">
+      <default>("",0,"")</default>
+      <summary>The conversation error</summary>
+      <description>
+        This is a serialized GError for the conversation that could hold a
+        kick, ban, or similar message.
+      </description>
+    </key>
+
+    <key name="favorite" type="b">
+      <default>false</default>
+      <summary>Whether or not conversation is a favorite</summary>
+      <description>
+        Users can mark conversations as favorites which allows user interfaces
+        to do interesting things with them.
+      </description>
+    </key>
+
+    <key name="federated" type="b">
+      <default>false</default>
+      <summary>Whether or not conversation is federated</summary>
+      <description>
+        In distribute chat networks, conversation can exist on other servers.
+        This property is used to mark them as such.
+      </description>
+    </key>
+
+    <key name="id" type="s">
+      <default>""</default>
+      <summary>The protocol specific identifier of the conversation</summary>
+      <description>
+        This is the the protocol specific part of the global identifier.
+      </description>
+    </key>
+
+    <key name="logging" type="b">
+      <default>false</default>
+      <summary>Whether or not the conversation is being logged</summary>
+      <description>
+        Conversations can have their logging toggled on and off and this stores
+        that.
+      </description>
+    </key>
+
+    <key name="name" type="s">
+      <default>""</default>
+      <summary>The name of the conversation</summary>
+      <description>
+        The user readable string describing of the conversation.
+      </description>
+    </key>
+
+    <key name="needs-attention" type="b">
+      <default>false</default>
+      <summary>Whether or not the conversation needs attention</summary>
+      <description>
+        A conversation can need attention when there are new messages, the user
+        has been mentioned, and so on.
+      </description>
+    </key>
+
+    <key name="tags" type="as">
+      <default>[]</default>
+      <summary>The tags for the conversation</summary>
+      <description>
+        The tags for the conversation.
+      </description>
+    </key>
+
+    <key name="title" type="s">
+      <default>""</default>
+      <summary>The title of the conversation</summary>
+      <description>
+        Certain protocols allow conversations to have a title as well as a
+        name.
+      </description>
+    </key>
+
+    <key name="topic" type="s">
+      <default>""</default>
+      <summary>The topic of the conversation</summary>
+      <description>
+        Topics are used to describe the purpose of a conversation as well as
+        provide additional information.
+      </description>
+    </key>
+
+    <key name="topic-author-id" type="s">
+      <default>""</default>
+      <summary>The id of the author of the topic</summary>
+      <description>
+        The id of the contact info that set the topic.
+      </description>
+    </key>
+
+    <key name="topic-updated" type="s">
+      <default>""</default>
+      <summary>The timestamp that the topic was last updated</summary>
+      <description>
+        An ISO-8601 timestamp of when the topic was last updated.
+      </description>
+    </key>
+
+    <key name="type" enum="im.pidgin.Purple.ConversationType">
+      <default>"unset"</default>
+      <summary>The type of the conversation</summary>
+      <description>
+        Conversations have different types, this defines which type the
+        conversation is.
+      </description>
+    </key>
+
+    <key name="user-nickname" type="s">
+      <default>""</default>
+      <summary>The conversation specific nickname for the user</summary>
+      <description>
+        Some protocols allow users to have a conversation specific nickname and
+        this settings holds that value.
+      </description>
+    </key>
+  </schema>
+</schemalist>
diff --git a/libpurple/data/meson.build b/libpurple/data/meson.build
--- a/libpurple/data/meson.build
+++ b/libpurple/data/meson.build
@@ -1,6 +1,7 @@
 schemas_dir = get_option('prefix') / get_option('datadir') / 'glib-2.0' / 'schemas'
 settings_schemas = [
     'im.pidgin.Purple.gschema.xml',
+    'im.pidgin.Purple.ConversationManager.gschema.xml',
     'im.pidgin.Purple.PresenceManager.gschema.xml',
 ]
 
diff --git a/libpurple/purpleconversationmanager.c b/libpurple/purpleconversationmanager.c
--- a/libpurple/purpleconversationmanager.c
+++ b/libpurple/purpleconversationmanager.c
@@ -20,6 +20,9 @@
  * this library; if not, see <https://www.gnu.org/licenses/>.
  */
 
+#define G_SETTINGS_ENABLE_BACKEND
+#include <gio/gsettingsbackend.h>
+
 #include "purpleconversationmanager.h"
 #include "purpleconversationmanagerprivate.h"
 
@@ -28,6 +31,8 @@
 #include "purpleui.h"
 #include "util.h"
 
+#define PURPLE_CONVERSATION_MANAGER_SETTINGS_KEY "conversation-manager-settings"
+
 enum {
 	PROP_0,
 	PROP_FILENAME,
@@ -50,6 +55,7 @@
 	GObject parent;
 
 	char *filename;
+	GSettingsBackend *backend;
 
 	GPtrArray *conversations;
 };
@@ -68,6 +74,13 @@
 	g_return_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager));
 
 	if(g_set_str(&manager->filename, filename)) {
+		if(!purple_strempty(manager->filename)) {
+			manager->backend = g_keyfile_settings_backend_new(manager->filename,
+			                                                  "/", NULL);
+		} else {
+			manager->backend = g_memory_settings_backend_new();
+		}
+
 		g_object_notify_by_pspec(G_OBJECT(manager), properties[PROP_FILENAME]);
 	}
 }
@@ -122,6 +135,195 @@
 	return NULL;
 }
 
+static GVariant *
+purple_conversation_manager_set_account_mapping(const GValue *value,
+                                                const GVariantType *expected,
+                                                G_GNUC_UNUSED gpointer data)
+{
+	PurpleAccount *account = NULL;
+	const char *account_id = NULL;
+
+	if(!g_variant_type_equal(expected, G_VARIANT_TYPE_STRING)) {
+		return NULL;
+	}
+
+	account = g_value_get_object(value);
+	if(!PURPLE_IS_ACCOUNT(account)) {
+		return NULL;
+	}
+
+	account_id = purple_account_get_id(account);
+	if(!purple_strempty(account_id)) {
+		GVariant *variant = NULL;
+
+		variant = g_variant_new_string(account_id);
+
+		return variant;
+	}
+
+	return NULL;
+}
+
+static GVariant *
+purple_conversation_manager_set_contact_info_mapping(const GValue *value,
+                                                     const GVariantType *expected,
+                                                     G_GNUC_UNUSED gpointer data)
+{
+	PurpleContactInfo *info = NULL;
+	const char *id = NULL;
+
+	if(!g_variant_type_equal(expected, G_VARIANT_TYPE_STRING)) {
+		return NULL;
+	}
+
+	info = g_value_get_object(value);
+	if(!PURPLE_IS_CONTACT_INFO(info)) {
+		return NULL;
+	}
+
+	id = purple_contact_info_get_id(info);
+	if(!purple_strempty(id)) {
+		GVariant *variant = NULL;
+
+		variant = g_variant_new_string(id);
+
+		return variant;
+	}
+
+	return NULL;
+}
+
+static GVariant *
+purple_conversation_manager_set_error_mapping(const GValue *value,
+                                              const GVariantType *expected,
+                                              G_GNUC_UNUSED gpointer data)
+{
+	GError *error = NULL;
+
+	if(!g_variant_type_equal(expected, "(sxs)")) {
+		return NULL;
+	}
+
+	error = g_value_get_boxed(value);
+	if(error == NULL) {
+		return NULL;
+	}
+
+	return g_variant_new("(sxs)", g_quark_to_string(error->domain),
+	                     error->code, error->message);
+}
+
+static GVariant *
+purple_conversation_manager_set_members_mapping(const GValue *value,
+                                                const GVariantType *expected,
+                                                G_GNUC_UNUSED gpointer data)
+{
+	PurpleConversationMembers *members = NULL;
+	GStrv strv = NULL;
+	GStrvBuilder *builder = NULL;
+	GVariant *variant = NULL;
+	guint n_items = 0;
+
+	if(!g_variant_type_equal(expected, G_VARIANT_TYPE_STRING_ARRAY)) {
+		return NULL;
+	}
+
+	members = g_value_get_object(value);
+	if(!PURPLE_IS_CONVERSATION_MEMBERS(members)) {
+		return NULL;
+	}
+
+	n_items = g_list_model_get_n_items(G_LIST_MODEL(members));
+	if(n_items == 0) {
+		return NULL;
+	}
+
+	builder = g_strv_builder_new();
+
+	for(guint i = 0; i < n_items; i++) {
+		PurpleContactInfo *info = NULL;
+		PurpleConversationMember *member = NULL;
+		const char *id = NULL;
+
+		member = g_list_model_get_item(G_LIST_MODEL(members), i);
+		info = purple_conversation_member_get_contact_info(member);
+		id = purple_contact_info_get_id(info);
+
+		g_strv_builder_add(builder, id);
+
+		g_clear_object(&member);
+	}
+
+	strv = g_strv_builder_end(builder);
+	variant = g_variant_new_strv((const char * const *)strv, -1);
+	g_strfreev(strv);
+	g_strv_builder_unref(builder);
+
+	return variant;
+}
+
+static GVariant *
+purple_conversation_manager_set_tags_mapping(const GValue *value,
+                                             const GVariantType *expected,
+                                             G_GNUC_UNUSED gpointer data)
+{
+	PurpleTags *tags = NULL;
+	GStrv strv = NULL;
+	GStrvBuilder *builder = NULL;
+	GVariant *variant = NULL;
+
+	if(!g_variant_type_equal(expected, G_VARIANT_TYPE_STRING_ARRAY)) {
+		return NULL;
+	}
+
+	tags = g_value_get_object(value);
+	if(!PURPLE_IS_TAGS(tags)) {
+		return NULL;
+	}
+
+	builder = g_strv_builder_new();
+
+	for(GList *i = purple_tags_get_all(tags); i != NULL; i = i->next) {
+		g_strv_builder_add(builder, i->data);
+	}
+
+	strv = g_strv_builder_end(builder);
+	variant = g_variant_new_strv((const char * const *)strv, -1);
+	g_strfreev(strv);
+	g_strv_builder_unref(builder);
+
+	return variant;
+}
+
+static GVariant *
+purple_conversation_manager_set_timestamp_mapping(const GValue *value,
+                                                  const GVariantType *expected,
+                                                  G_GNUC_UNUSED gpointer data)
+{
+	GDateTime *datetime = NULL;
+	char *timestamp = NULL;
+
+	if(!g_variant_type_equal(expected, G_VARIANT_TYPE_STRING)) {
+		return NULL;
+	}
+
+	datetime = g_value_get_boxed(value);
+	if(datetime == NULL) {
+		return NULL;
+	}
+
+	timestamp = g_date_time_format_iso8601(datetime);
+	if(timestamp != NULL) {
+		GVariant *variant = NULL;
+
+		variant = g_variant_new_take_string(timestamp);
+
+		return variant;
+	}
+
+	return NULL;
+}
+
 /******************************************************************************
  * Callbacks
  *****************************************************************************/
@@ -186,9 +388,6 @@
                               G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL,
                                                     pidgin_conversation_manager_list_model_iface_init))
 
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
 static void
 purple_conversation_manager_init(PurpleConversationManager *manager) {
 	manager->conversations = g_ptr_array_new_full(10, g_object_unref);
@@ -200,6 +399,7 @@
 
 	g_clear_pointer(&manager->conversations, g_ptr_array_unref);
 	g_clear_pointer(&manager->filename, g_free);
+	g_clear_object(&manager->backend);
 
 	G_OBJECT_CLASS(purple_conversation_manager_parent_class)->finalize(obj);
 }
@@ -418,6 +618,7 @@
 purple_conversation_manager_add(PurpleConversationManager *manager,
                                 PurpleConversation *conversation)
 {
+	GSettings *settings = NULL;
 	guint position = 0;
 
 	g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), FALSE);
@@ -430,6 +631,107 @@
 	position = manager->conversations->len;
 	g_ptr_array_add(manager->conversations, g_object_ref(conversation));
 
+	/* Check if the conversation already had a settings object attached to it. */
+	settings = g_object_get_data(G_OBJECT(conversation),
+	                             PURPLE_CONVERSATION_MANAGER_SETTINGS_KEY);
+	if(!G_IS_SETTINGS(settings)) {
+		char *path = NULL;
+
+		path = g_strdup_printf("/%s/",
+		                       purple_conversation_get_global_id(conversation));
+		settings = g_settings_new_with_backend_and_path("im.pidgin.Purple.ConversationManager.Conversation",
+		                                                manager->backend,
+		                                                path);
+		g_free(path);
+
+		g_object_set_data_full(G_OBJECT(conversation),
+		                       PURPLE_CONVERSATION_MANAGER_SETTINGS_KEY,
+		                       settings,
+		                       g_object_unref);
+
+		g_settings_bind_with_mapping(settings, "account-id",
+		                             conversation, "account",
+		                             G_SETTINGS_BIND_SET,
+		                             NULL,
+		                             purple_conversation_manager_set_account_mapping,
+		                             NULL, NULL);
+		g_settings_bind(settings, "age-restricted",
+		                conversation, "age-restricted",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind(settings, "alias",
+		                conversation, "alias",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind_with_mapping(settings, "created-on",
+		                             conversation, "created-on",
+		                             G_SETTINGS_BIND_SET,
+		                             NULL,
+		                             purple_conversation_manager_set_timestamp_mapping,
+		                             NULL, NULL);
+		g_settings_bind_with_mapping(settings, "creator-id",
+		                             conversation, "creator",
+		                             G_SETTINGS_BIND_SET,
+		                             NULL,
+		                             purple_conversation_manager_set_contact_info_mapping,
+		                             NULL, NULL);
+		g_settings_bind(settings, "description",
+		                conversation, "description",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind(settings, "drafting",
+		                conversation, "drafting",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind_with_mapping(settings, "error",
+		                             conversation, "error",
+		                             G_SETTINGS_BIND_SET,
+		                             NULL,
+		                             purple_conversation_manager_set_error_mapping,
+		                             NULL, NULL);
+		g_settings_bind(settings, "favorite",
+		                conversation, "favorite",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind(settings, "federated",
+		                conversation, "federated",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind(settings, "id",
+		                conversation, "id",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind(settings, "name",
+		                conversation, "name",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind(settings, "needs-attention",
+		                conversation, "needs-attention",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind_with_mapping(settings, "tags",
+		                             conversation, "tags",
+		                             G_SETTINGS_BIND_SET,
+		                             NULL,
+		                             purple_conversation_manager_set_tags_mapping,
+		                             NULL, NULL);
+		g_settings_bind(settings, "title",
+		                conversation, "title",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind(settings, "topic",
+		                conversation, "topic",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind_with_mapping(settings, "topic-author-id",
+		                             conversation, "topic-author",
+		                             G_SETTINGS_BIND_SET,
+		                             NULL,
+		                             purple_conversation_manager_set_contact_info_mapping,
+		                             NULL, NULL);
+		g_settings_bind_with_mapping(settings, "topic-updated",
+		                             conversation, "topic-updated",
+		                             G_SETTINGS_BIND_SET,
+		                             NULL,
+		                             purple_conversation_manager_set_timestamp_mapping,
+		                             NULL, NULL);
+		g_settings_bind(settings, "type",
+		                conversation, "type",
+		                G_SETTINGS_BIND_SET);
+		g_settings_bind(settings, "user-nickname",
+		                conversation, "user-nickname",
+		                G_SETTINGS_BIND_SET);
+	}
+
 	/* Register our signals that need to be propagated. */
 	g_signal_connect_object(conversation, "notify",
 	                        G_CALLBACK(purple_conversation_manager_conversation_changed_cb),
