# HG changeset patch
# Node ID 58aa9f998c8f
# Parent  6f7cb0a641d9
diff --git a/libpurple/data/im.pidgin.Purple.PresenceManager.gschema.xml b/libpurple/data/im.pidgin.Purple.PresenceManager.gschema.xml
new file mode 100644
--- /dev/null
+++ b/libpurple/data/im.pidgin.Purple.PresenceManager.gschema.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<schemalist>
+  <enum id="im.pidgin.Purple.PresencePrimitive">
+    <value nick="offline" value="0"/>
+    <value nick="available" value="1"/>
+    <value nick="idle" value="2"/>
+    <value nick="invisible" value="3"/>
+    <value nick="away" value="4"/>
+    <value nick="extended away" value="5"/>
+    <value nick="streaming" value="6"/>
+  </enum>
+
+  <schema id="im.pidgin.Purple.SavedPresence">
+    <key name="last-used" type="s">
+      <default>""</default>
+      <summary>The last time this presence was used</summary>
+      <description>
+        An ISO-8601 formatted time stamp of the last time this presence was used.
+      </description>
+    </key>
+
+    <key name="use-count" type="t">
+      <default>0</default>
+      <summary>The number of times this presence has been used.</summary>
+      <description>
+        A counter for how many times the user has used this presence.
+      </description>
+    </key>
+
+    <key name="name" type="s">
+      <default>""</default>
+      <summary>The name of this presence</summary>
+      <description>
+        A name for this presence. If this is empty or otherwise not provided a random name will be generated.
+      </description>
+    </key>
+
+    <key name="primitive" enum="im.pidgin.Purple.PresencePrimitive">
+      <default>"offline"</default>
+      <summary>The primitive type</summary>
+      <description>
+        The PurplePresencePrimitive for this presence.
+      </description>
+    </key>
+
+    <key name="message" type="s">
+      <default>""</default>
+      <summary>A message for this presence</summary>
+      <description>
+        A user supplied message for this presence. This could be something like "Be back later..", "At work", etc.
+      </description>
+    </key>
+
+    <key name="emoji" type="s">
+      <default>""</default>
+      <summary>An emoji to associate with this status</summary>
+      <description>
+        An emoji is typically associated with a status to convey a user's mood or just for fun. Either way this field handles that case.
+      </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
@@ -14,7 +14,8 @@
 
 schemas_dir = get_option('prefix') / get_option('datadir') / 'glib-2.0' / 'schemas'
 settings_schemas = [
-  'im.pidgin.Purple.gschema.xml',
+    'im.pidgin.Purple.gschema.xml',
+    'im.pidgin.Purple.PresenceManager.gschema.xml',
 ]
 
 install_data(settings_schemas, install_dir: schemas_dir)
diff --git a/libpurple/purplesavedpresence.h b/libpurple/purplesavedpresence.h
--- a/libpurple/purplesavedpresence.h
+++ b/libpurple/purplesavedpresence.h
@@ -73,7 +73,7 @@
  *
  * Since: 3.0.0
  */
-guint purple_saved_presence_get_use_count(PurpleSavedPresence *presence);
+guint64 purple_saved_presence_get_use_count(PurpleSavedPresence *presence);
 
 /**
  * purple_saved_presence_set_use_count:
@@ -84,7 +84,7 @@
  *
  * Since: 3.0.0
  */
-void purple_saved_presence_set_use_count(PurpleSavedPresence *presence, guint use_count);
+void purple_saved_presence_set_use_count(PurpleSavedPresence *presence, guint64 use_count);
 
 /**
  * purple_saved_presence_get_id:
diff --git a/libpurple/purplesavedpresence.c b/libpurple/purplesavedpresence.c
--- a/libpurple/purplesavedpresence.c
+++ b/libpurple/purplesavedpresence.c
@@ -29,8 +29,10 @@
 struct _PurpleSavedPresence {
 	GObject parent;
 
+	GSettings *settings;
+
 	GDateTime *last_used;
-	guint use_count;
+	guint64 use_count;
 
 	char *id;
 	char *name;
@@ -42,6 +44,7 @@
 
 enum {
 	PROP_0,
+	PROP_SETTINGS,
 	PROP_LAST_USED,
 	PROP_USE_COUNT,
 	PROP_ID,
@@ -58,6 +61,108 @@
 /******************************************************************************
  * Helpers
  *****************************************************************************/
+static gboolean
+purple_saved_presence_get_last_used_mapping(GValue *value, GVariant *variant,
+                                            G_GNUC_UNUSED gpointer data)
+{
+	GDateTime *datetime = NULL;
+	GTimeZone *tz = NULL;
+	const char *timestamp = NULL;
+
+	timestamp = g_variant_get_string(variant, NULL);
+	tz = g_time_zone_new_utc();
+	datetime = g_date_time_new_from_iso8601(timestamp, tz);
+	g_time_zone_unref(tz);
+
+	if(datetime != NULL) {
+		g_value_take_boxed(value, datetime);
+	}
+
+	return TRUE;
+}
+
+static GVariant *
+purple_saved_presence_set_last_used_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);
+	timestamp = g_date_time_format_iso8601(datetime);
+	if(timestamp != NULL) {
+		GVariant *variant = NULL;
+
+		variant = g_variant_new_string(timestamp);
+		g_free(timestamp);
+
+		return variant;
+	}
+
+	return NULL;
+}
+
+static void
+purple_saved_presence_bind_settings(PurpleSavedPresence *presence,
+                                    GSettings *settings)
+{
+	g_return_if_fail(PURPLE_IS_SAVED_PRESENCE(presence));
+	g_return_if_fail(G_IS_SETTINGS(settings));
+
+	g_settings_bind_with_mapping(settings, "last-used",
+	                             presence, "last-used",
+	                             G_SETTINGS_BIND_DEFAULT,
+	                             purple_saved_presence_get_last_used_mapping,
+	                             purple_saved_presence_set_last_used_mapping,
+	                             NULL, NULL);
+	g_settings_bind(settings, "use-count", presence, "use-count",
+	                G_SETTINGS_BIND_DEFAULT);
+	g_settings_bind(settings, "name", presence, "name",
+	                G_SETTINGS_BIND_DEFAULT);
+	g_settings_bind(settings, "primitive", presence, "primitive",
+	                G_SETTINGS_BIND_DEFAULT);
+	g_settings_bind(settings, "message", presence, "message",
+	                G_SETTINGS_BIND_DEFAULT);
+	g_settings_bind(settings, "emoji", presence, "emoji",
+	                G_SETTINGS_BIND_DEFAULT);
+}
+
+static void
+purple_saved_presence_set_settings(PurpleSavedPresence *presence,
+                                   GSettings *settings)
+{
+	g_return_if_fail(PURPLE_IS_SAVED_PRESENCE(presence));
+
+	if(G_IS_SETTINGS(settings)) {
+		char *schema_id = NULL;
+
+		g_object_get(G_OBJECT(settings), "schema-id", &schema_id, NULL);
+
+		if(!purple_strequal("im.pidgin.Purple.SavedPresence", schema_id)) {
+			g_warning("expected schema id of im.pidgin.Purple.SavedPresence, "
+			          "but found %s", schema_id);
+
+			g_free(schema_id);
+
+			return;
+		}
+
+		g_free(schema_id);
+	}
+
+	if(g_set_object(&presence->settings, settings)) {
+		g_object_notify_by_pspec(G_OBJECT(presence),
+		                         properties[PROP_SETTINGS]);
+
+		purple_saved_presence_bind_settings(presence, settings);
+	}
+}
+
 static void
 purple_saved_presence_set_id(PurpleSavedPresence *presence, const char *id) {
 	g_return_if_fail(PURPLE_IS_SAVED_PRESENCE(presence));
@@ -95,13 +200,17 @@
 	PurpleSavedPresence *presence = PURPLE_SAVED_PRESENCE(obj);
 
 	switch(param_id) {
+		case PROP_SETTINGS:
+			purple_saved_presence_set_settings(presence,
+			                                   g_value_get_object(value));
+			break;
 		case PROP_LAST_USED:
 			purple_saved_presence_set_last_used(presence,
 			                                    g_value_get_boxed(value));
 			break;
 		case PROP_USE_COUNT:
 			purple_saved_presence_set_use_count(presence,
-			                                    g_value_get_uint(value));
+			                                    g_value_get_uint64(value));
 			break;
 		case PROP_ID:
 			purple_saved_presence_set_id(presence, g_value_get_string(value));
@@ -140,8 +249,8 @@
 			                  purple_saved_presence_get_last_used(presence));
 			break;
 		case PROP_USE_COUNT:
-			g_value_set_uint(value,
-			                 purple_saved_presence_get_use_count(presence));
+			g_value_set_uint64(value,
+			                   purple_saved_presence_get_use_count(presence));
 			break;
 		case PROP_ID:
 			g_value_set_string(value,
@@ -177,6 +286,8 @@
 purple_saved_presence_finalize(GObject *obj) {
 	PurpleSavedPresence *presence = PURPLE_SAVED_PRESENCE(obj);
 
+	g_clear_object(&presence->settings);
+
 	g_clear_pointer(&presence->last_used, g_date_time_unref);
 
 	g_clear_pointer(&presence->id, g_free);
@@ -197,6 +308,24 @@
 	obj_class->set_property = purple_saved_presence_set_property;
 
 	/**
+	 * PurpleSavedPresence:settings:
+	 *
+	 * The [class@Gio.Settings] for this saved presence. This settings object
+	 * is typically created by PurplePresenceManager and is expecting the
+	 * im.pidgin.Purple.SavedPresence schema.
+	 *
+	 * When this is non-null, this saved presence will bind all of its
+	 * properties to the settings object.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_SETTINGS] = g_param_spec_object(
+		"settings", "settings",
+		"The GSettings for this saved presence.",
+		G_TYPE_SETTINGS,
+		G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
 	 * PurpleSavedPresence:last-used:
 	 *
 	 * The [struct@GLib.DateTime] when this saved presence was last used.
@@ -216,10 +345,10 @@
 	 *
 	 * Since: 3.0.0
 	 */
-	properties[PROP_USE_COUNT] = g_param_spec_uint(
+	properties[PROP_USE_COUNT] = g_param_spec_uint64(
 		"use-count", "use-count",
 		"The number of times this saved presence has been used.",
-		0, G_MAXUINT, 0,
+		0, G_MAXUINT64, 0,
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -319,7 +448,7 @@
 	}
 }
 
-guint
+guint64
 purple_saved_presence_get_use_count(PurpleSavedPresence *presence) {
 	g_return_val_if_fail(PURPLE_IS_SAVED_PRESENCE(presence), 0);
 
@@ -328,7 +457,7 @@
 
 void
 purple_saved_presence_set_use_count(PurpleSavedPresence *presence,
-                                    guint use_count)
+                                    guint64 use_count)
 {
 	g_return_if_fail(PURPLE_IS_SAVED_PRESENCE(presence));
 
diff --git a/libpurple/tests/test_saved_presence.c b/libpurple/tests/test_saved_presence.c
--- a/libpurple/tests/test_saved_presence.c
+++ b/libpurple/tests/test_saved_presence.c
@@ -18,8 +18,13 @@
 
 #include <glib.h>
 
+#define G_SETTINGS_ENABLE_BACKEND
+#include <gio/gsettingsbackend.h>
+
 #include <purple.h>
 
+#define SCHEMA_ID "im.pidgin.Purple.SavedPresence"
+
 /******************************************************************************
  * Tests
  *****************************************************************************/
@@ -29,7 +34,7 @@
 	PurplePresencePrimitive primitive;
 	GDateTime *last_used = NULL;
 	GDateTime *last_used1 = NULL;
-	guint use_count;
+	guint64 use_count;
 	char *id = NULL;
 	char *name = NULL;
 	char *message = NULL;
@@ -105,6 +110,118 @@
 }
 
 static void
+test_purple_saved_presence_set_settings(void) {
+	PurpleSavedPresence *presence = NULL;
+	PurplePresencePrimitive primitive = PURPLE_PRESENCE_PRIMITIVE_OFFLINE;
+	GSettingsBackend *backend = NULL;
+	GSettings *settings = NULL;
+	GDateTime *last_used = NULL;
+	guint64 use_count = 0;
+	const char *name = NULL;
+	const char *message = NULL;
+	const char *emoji = NULL;
+	char *timestamp = NULL;
+
+	backend = g_memory_settings_backend_new();
+	settings = g_settings_new_with_backend_and_path(SCHEMA_ID, backend, "/");
+
+	presence = g_object_new(
+		PURPLE_TYPE_SAVED_PRESENCE,
+		"settings", settings,
+		NULL);
+
+	/* Set each setting and verify that the property matches what we set. */
+	g_settings_set_string(settings, "last-used", "2023-07-06T05:32:24Z");
+	last_used = purple_saved_presence_get_last_used(presence);
+	g_assert_nonnull(last_used);
+	timestamp = g_date_time_format_iso8601(last_used);
+	g_assert_cmpstr(timestamp, ==, "2023-07-06T05:32:24Z");
+	g_free(timestamp);
+
+	g_settings_set_uint64(settings, "use-count", 42);
+	use_count = purple_saved_presence_get_use_count(presence);
+	g_assert_cmpuint(use_count, ==, 42);
+
+	g_settings_set_string(settings, "name", "brb");
+	name = purple_saved_presence_get_name(presence);
+	g_assert_cmpstr(name, ==, "brb");
+
+	g_settings_set_enum(settings, "primitive", PURPLE_PRESENCE_PRIMITIVE_AWAY);
+	primitive = purple_saved_presence_get_primitive(presence);
+	g_assert_cmpuint(primitive, ==, PURPLE_PRESENCE_PRIMITIVE_AWAY);
+
+	g_settings_set_string(settings, "message", "message in a bottle");
+	message = purple_saved_presence_get_message(presence);
+	g_assert_cmpstr(message, ==, "message in a bottle");
+
+	g_settings_set_string(settings, "emoji", "🍾");
+	emoji = purple_saved_presence_get_emoji(presence);
+	g_assert_cmpstr(emoji, ==, "🍾");
+
+	g_clear_object(&presence);
+	g_clear_object(&settings);
+	g_clear_object(&backend);
+}
+
+static void
+test_purple_saved_presence_set_properties(void) {
+	PurpleSavedPresence *presence = NULL;
+	PurplePresencePrimitive primitive = PURPLE_PRESENCE_PRIMITIVE_OFFLINE;
+	GSettingsBackend *backend = NULL;
+	GSettings *settings = NULL;
+	GDateTime *last_used = NULL;
+	guint64 use_count = 0;
+	char *name = NULL;
+	char *message = NULL;
+	char *emoji = NULL;
+	char *timestamp = NULL;
+
+	backend = g_memory_settings_backend_new();
+	settings = g_settings_new_with_backend_and_path(SCHEMA_ID, backend, "/");
+
+	presence = g_object_new(
+		PURPLE_TYPE_SAVED_PRESENCE,
+		"settings", settings,
+		NULL);
+
+	/* Set each setting and verify that the property matches what we set. */
+	last_used = g_date_time_new_from_iso8601("2023-07-06T05:32:24Z", NULL);
+	purple_saved_presence_set_last_used(presence, last_used);
+	g_date_time_unref(last_used);
+	timestamp = g_settings_get_string(settings, "last-used");
+	g_assert_cmpstr(timestamp, ==, "2023-07-06T05:32:24Z");
+	g_free(timestamp);
+
+	purple_saved_presence_set_use_count(presence, 42);
+	use_count = g_settings_get_uint64(settings, "use-count");
+	g_assert_cmpuint(use_count, ==, 42);
+
+	purple_saved_presence_set_name(presence, "brb");
+	name = g_settings_get_string(settings, "name");
+	g_assert_cmpstr(name, ==, "brb");
+	g_free(name);
+
+	purple_saved_presence_set_primitive(presence,
+	                                    PURPLE_PRESENCE_PRIMITIVE_AWAY);
+	primitive = g_settings_get_enum(settings, "primitive");
+	g_assert_cmpuint(primitive, ==, PURPLE_PRESENCE_PRIMITIVE_AWAY);
+
+	purple_saved_presence_set_message(presence, "message in a bottle");
+	message = g_settings_get_string(settings, "message");
+	g_assert_cmpstr(message, ==, "message in a bottle");
+	g_free(message);
+
+	purple_saved_presence_set_emoji(presence, "🍾");
+	emoji = g_settings_get_string(settings, "emoji");
+	g_assert_cmpstr(emoji, ==, "🍾");
+	g_free(emoji);
+
+	g_clear_object(&presence);
+	g_clear_object(&settings);
+	g_clear_object(&backend);
+}
+
+static void
 test_purple_saved_presence_equal_null_null(void) {
 	g_assert_true(purple_saved_presence_equal(NULL, NULL));
 }
@@ -295,6 +412,11 @@
 	g_test_add_func("/saved-presence/generates-id",
 	                test_purple_saved_presence_generates_id);
 
+	g_test_add_func("/saved-presence/settings/set-settings",
+	                test_purple_saved_presence_set_settings);
+	g_test_add_func("/saved-presence/settings/set-properties",
+	                test_purple_saved_presence_set_properties);
+
 	g_test_add_func("/saved-presence/equal/null_null",
 	                test_purple_saved_presence_equal_null_null);
 	g_test_add_func("/saved-presence/equal/null_a",
