# HG changeset patch
# Node ID bda4e8eee1f8
# Parent  cfa7c940ed62
diff --git a/libpurple/meson.build b/libpurple/meson.build
--- a/libpurple/meson.build
+++ b/libpurple/meson.build
@@ -88,6 +88,7 @@
 	'purplerequestgroup.c',
 	'purplerequestpage.c',
 	'purpleroomlistroom.c',
+	'purplesavedpresence.c',
 	'purplesqlite3.c',
 	'purplesqlitehistoryadapter.c',
 	'purpletags.c',
@@ -205,6 +206,7 @@
 	'purplerequestgroup.h',
 	'purplerequestpage.h',
 	'purpleroomlistroom.h',
+	'purplesavedpresence.h',
 	'purplesqlite3.h',
 	'purplesqlitehistoryadapter.h',
 	'purpletags.h',
diff --git a/libpurple/purplesavedpresence.h b/libpurple/purplesavedpresence.h
new file mode 100644
--- /dev/null
+++ b/libpurple/purplesavedpresence.h
@@ -0,0 +1,197 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
+# error "only <purple.h> may be included directly"
+#endif
+
+#ifndef PURPLE_SAVED_PRESENCE_H
+#define PURPLE_SAVED_PRESENCE_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <libpurple/purplepresence.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_SAVED_PRESENCE purple_saved_presence_get_type()
+G_DECLARE_FINAL_TYPE(PurpleSavedPresence, purple_saved_presence, PURPLE,
+                     SAVED_PRESENCE, GObject)
+
+/**
+ * purple_saved_presence_new:
+ *
+ * Creates a new saved_presence instance.
+ *
+ * Returns: (transfer full): The new instance.
+ *
+ * Since: 3.0.0
+ */
+PurpleSavedPresence *purple_saved_presence_new(void);
+
+/**
+ * purple_saved_presence_get_last_used:
+ * @presence: The instance.
+ *
+ * Gets the [struct@GLib.DateTime] that @presence was last used.
+ *
+ * Returns: (transfer none) (nullable): The time @presence was last used or
+ *          %NULL.
+ *
+ * Since: 3.0.0
+ */
+GDateTime *purple_saved_presence_get_last_used(PurpleSavedPresence *presence);
+
+/**
+ * purple_saved_presence_set_last_used:
+ * @presence: The instance.
+ * @last_used: (nullable): The time this was last used.
+ *
+ * Sets the last time @presence was used to @last_used. If @last_used is %NULL,
+ * the time will be cleared.
+ *
+ * Since: 3.0.0
+ */
+void purple_saved_presence_set_last_used(PurpleSavedPresence *presence, GDateTime *last_used);
+
+/**
+ * purple_saved_presence_get_use_count:
+ * @presence: The instance.
+ *
+ * Gets the number of times @presence has been used.
+ *
+ * Returns: The number of times @presence has been used.
+ *
+ * Since: 3.0.0
+ */
+guint purple_saved_presence_get_use_count(PurpleSavedPresence *presence);
+
+/**
+ * purple_saved_presence_set_use_count:
+ * @presence: The instance.
+ * @use_count: The new use count.
+ *
+ * Sets the number of times @presence has been used to @use_count.
+ *
+ * Since: 3.0.0
+ */
+void purple_saved_presence_set_use_count(PurpleSavedPresence *presence, guint use_count);
+
+/**
+ * purple_saved_presence_get_name:
+ * @presence: The instance.
+ *
+ * Gets the name of @presence.
+ *
+ * Returns: (nullable): The name of @presence.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_saved_presence_get_name(PurpleSavedPresence *presence);
+
+/**
+ * purple_saved_presence_set_name:
+ * @presence: The instance.
+ * @name: (nullable): The new name.
+ *
+ * Sets the name of @presence to @name. If @name is %NULL the name will be
+ * cleared.
+ *
+ * Since: 3.0.0
+ */
+void purple_saved_presence_set_name(PurpleSavedPresence *presence, const char *name);
+
+/**
+ * purple_saved_presence_get_primitive:
+ * @presence: The instance.
+ *
+ * Gets the [enum@PresencePrimitive] for @presence.
+ *
+ * Returns: The [enum@PresencePrimitive] for @presence.
+ *
+ * Since: 3.0.0
+ */
+PurplePresencePrimitive purple_saved_presence_get_primitive(PurpleSavedPresence *presence);
+
+/**
+ * purple_saved_presence_set_primitive:
+ * @presence: The instance.
+ * @primitive: The new primitive.
+ *
+ * Sets the [enum@PresencePrimitive] of @presence to @primitive.
+ *
+ * Since: 3.0.0
+ */
+void purple_saved_presence_set_primitive(PurpleSavedPresence *presence, PurplePresencePrimitive primitive);
+
+/**
+ * purple_saved_presence_get_message:
+ * @presence: The instance.
+ *
+ * Gets the message of @presence.
+ *
+ * Returns: (nullable): The message from @presence.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_saved_presence_get_message(PurpleSavedPresence *presence);
+
+/**
+ * purple_saved_presence_set_message:
+ * @presence: The instance.
+ * @message: (nullable): The new message.
+ *
+ * Sets the message of @presence to @message. If @message is %NULL the message
+ * is cleared.
+ *
+ * Since: 3.0.0
+ */
+void purple_saved_presence_set_message(PurpleSavedPresence *presence, const char *message);
+
+/**
+ * purple_saved_presence_get_emoji:
+ * @presence: The instance.
+ *
+ * Gets the emoji for @presence.
+ *
+ * Returns: (nullable): The emoji for @presence.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_saved_presence_get_emoji(PurpleSavedPresence *presence);
+
+/**
+ * purple_saved_presence_set_emoji:
+ * @presence: The instance.
+ * @emoji: (nullable): The new emoji.
+ *
+ * Sets the emoji of @presence to @emoji. If @emoji is %NULL, the emoji will be
+ * cleared.
+ *
+ * Since: 3.0.0
+ */
+void purple_saved_presence_set_emoji(PurpleSavedPresence *presence, const char *emoji);
+
+G_END_DECLS
+
+#endif /* PURPLE_SAVED_PRESENCE_H */
diff --git a/libpurple/purplesavedpresence.c b/libpurple/purplesavedpresence.c
new file mode 100644
--- /dev/null
+++ b/libpurple/purplesavedpresence.c
@@ -0,0 +1,376 @@
+/*
+ * purple
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n-lib.h>
+
+#include "purpleenums.h"
+#include "purplesavedpresence.h"
+#include "util.h"
+
+struct _PurpleSavedPresence {
+	GObject parent;
+
+	GDateTime *last_used;
+	guint use_count;
+
+	char *name;
+
+	PurplePresencePrimitive primitive;
+	char *message;
+	char *emoji;
+};
+
+enum {
+	PROP_0,
+	PROP_LAST_USED,
+	PROP_USE_COUNT,
+	PROP_NAME,
+	PROP_PRIMITIVE,
+	PROP_MESSAGE,
+	PROP_EMOJI,
+	N_PROPERTIES
+};
+static GParamSpec *properties[N_PROPERTIES];
+
+G_DEFINE_TYPE(PurpleSavedPresence, purple_saved_presence, G_TYPE_OBJECT)
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+purple_saved_presence_set_property(GObject *obj, guint param_id,
+                                   const GValue *value, GParamSpec *pspec)
+{
+	PurpleSavedPresence *presence = PURPLE_SAVED_PRESENCE(obj);
+
+	switch(param_id) {
+		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));
+			break;
+		case PROP_NAME:
+			purple_saved_presence_set_name(presence,
+			                               g_value_get_string(value));
+			break;
+		case PROP_PRIMITIVE:
+			purple_saved_presence_set_primitive(presence,
+			                                    g_value_get_enum(value));
+			break;
+		case PROP_MESSAGE:
+			purple_saved_presence_set_message(presence,
+			                                  g_value_get_string(value));
+			break;
+		case PROP_EMOJI:
+			purple_saved_presence_set_emoji(presence,
+			                                g_value_get_string(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_saved_presence_get_property(GObject *obj, guint param_id, GValue *value,
+                                   GParamSpec *pspec)
+{
+	PurpleSavedPresence *presence = PURPLE_SAVED_PRESENCE(obj);
+
+	switch(param_id) {
+		case PROP_LAST_USED:
+			g_value_set_boxed(value,
+			                  purple_saved_presence_get_last_used(presence));
+			break;
+		case PROP_USE_COUNT:
+			g_value_set_uint(value,
+			                 purple_saved_presence_get_use_count(presence));
+			break;
+		case PROP_NAME:
+			g_value_set_string(value,
+			                   purple_saved_presence_get_name(presence));
+			break;
+		case PROP_PRIMITIVE:
+			g_value_set_enum(value,
+			                 purple_saved_presence_get_primitive(presence));
+			break;
+		case PROP_MESSAGE:
+			g_value_set_string(value,
+			                   purple_saved_presence_get_message(presence));
+			break;
+		case PROP_EMOJI:
+			g_value_set_string(value,
+			                   purple_saved_presence_get_emoji(presence));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_saved_presence_init(G_GNUC_UNUSED PurpleSavedPresence *presence) {
+}
+
+static void
+purple_saved_presence_finalize(GObject *obj) {
+	PurpleSavedPresence *presence = PURPLE_SAVED_PRESENCE(obj);
+
+	g_clear_pointer(&presence->last_used, g_date_time_unref);
+
+	g_clear_pointer(&presence->name, g_free);
+	g_clear_pointer(&presence->message, g_free);
+	g_clear_pointer(&presence->emoji, g_free);
+
+	G_OBJECT_CLASS(purple_saved_presence_parent_class)->finalize(obj);
+}
+
+static void
+purple_saved_presence_class_init(PurpleSavedPresenceClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->get_property = purple_saved_presence_get_property;
+	obj_class->set_property = purple_saved_presence_set_property;
+	obj_class->finalize = purple_saved_presence_finalize;
+
+	/**
+	 * PurpleSavedPresence:last-used:
+	 *
+	 * The [struct@GLib.DateTime] when this saved presence was last used.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_LAST_USED] = g_param_spec_boxed(
+		"last-used", "last-used",
+		"The time this presence was last used.",
+		G_TYPE_DATE_TIME,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleSavedPresence:use-count:
+	 *
+	 * The number of times this saved presence has been used.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_USE_COUNT] = g_param_spec_uint(
+		"use-count", "use-count",
+		"The number of times this saved presence has been used.",
+		0, G_MAXUINT, 0,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleSavedPresence:name:
+	 *
+	 * The name of the saved presence.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_NAME] = g_param_spec_string(
+		"name", "name",
+		"The name of this saved presence.",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleSavedPresence:primitive:
+	 *
+	 * The [enum@Purple.StatusPrimitive] for this saved presence.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_PRIMITIVE] = g_param_spec_enum(
+		"primitive", "primitive",
+		"The primitive for this saved presence.",
+		PURPLE_TYPE_PRESENCE_PRIMITIVE,
+		PURPLE_PRESENCE_PRIMITIVE_OFFLINE,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleSavedPresence:message:
+	 *
+	 * The status message of this saved presence.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_MESSAGE] = g_param_spec_string(
+		"message", "message",
+		"The status message of this saved presence.",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleSavedPresence:emoji:
+	 *
+	 * The emoji or mood of the presence.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_EMOJI] = g_param_spec_string(
+		"emoji", "emoji",
+		"The emoji for this saved presence.",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+PurpleSavedPresence *
+purple_saved_presence_new(void) {
+	return g_object_new(PURPLE_TYPE_SAVED_PRESENCE, NULL);
+}
+
+GDateTime *
+purple_saved_presence_get_last_used(PurpleSavedPresence *presence) {
+	g_return_val_if_fail(PURPLE_IS_SAVED_PRESENCE(presence), NULL);
+
+	return presence->last_used;
+}
+
+void
+purple_saved_presence_set_last_used(PurpleSavedPresence *presence,
+                                    GDateTime *last_used)
+{
+	g_return_if_fail(PURPLE_IS_SAVED_PRESENCE(presence));
+
+	if(presence->last_used != last_used) {
+		g_clear_pointer(&presence->last_used, g_date_time_unref);
+		if(last_used != NULL) {
+			presence->last_used = g_date_time_ref(last_used);
+		}
+
+		g_object_notify_by_pspec(G_OBJECT(presence),
+		                         properties[PROP_LAST_USED]);
+	}
+}
+
+guint
+purple_saved_presence_get_use_count(PurpleSavedPresence *presence) {
+	g_return_val_if_fail(PURPLE_IS_SAVED_PRESENCE(presence), 0);
+
+	return presence->use_count;
+}
+
+void
+purple_saved_presence_set_use_count(PurpleSavedPresence *presence,
+                                    guint use_count)
+{
+	g_return_if_fail(PURPLE_IS_SAVED_PRESENCE(presence));
+
+	if(presence->use_count != use_count) {
+		presence->use_count = use_count;
+
+		g_object_notify_by_pspec(G_OBJECT(presence),
+		                         properties[PROP_USE_COUNT]);
+	}
+}
+
+const char *
+purple_saved_presence_get_name(PurpleSavedPresence *presence) {
+	g_return_val_if_fail(PURPLE_IS_SAVED_PRESENCE(presence), NULL);
+
+	return presence->name;
+}
+
+void
+purple_saved_presence_set_name(PurpleSavedPresence *presence,
+                               const char *name)
+{
+	g_return_if_fail(PURPLE_IS_SAVED_PRESENCE(presence));
+
+	if(!purple_strequal(presence->name, name)) {
+		g_free(presence->name);
+		presence->name = g_strdup(name);
+
+		g_object_notify_by_pspec(G_OBJECT(presence), properties[PROP_NAME]);
+	}
+}
+
+PurplePresencePrimitive
+purple_saved_presence_get_primitive(PurpleSavedPresence *presence) {
+	g_return_val_if_fail(PURPLE_IS_SAVED_PRESENCE(presence),
+	                     PURPLE_PRESENCE_PRIMITIVE_OFFLINE);
+
+	return presence->primitive;
+}
+
+void
+purple_saved_presence_set_primitive(PurpleSavedPresence *presence,
+                                    PurplePresencePrimitive primitive)
+{
+	g_return_if_fail(PURPLE_IS_SAVED_PRESENCE(presence));
+
+	if(presence->primitive != primitive) {
+		presence->primitive = primitive;
+
+		g_object_notify_by_pspec(G_OBJECT(presence),
+		                         properties[PROP_PRIMITIVE]);
+	}
+}
+
+const char *
+purple_saved_presence_get_message(PurpleSavedPresence *presence) {
+	g_return_val_if_fail(PURPLE_IS_SAVED_PRESENCE(presence), NULL);
+
+	return presence->message;
+}
+
+void
+purple_saved_presence_set_message(PurpleSavedPresence *presence,
+                                  const char *message)
+{
+	g_return_if_fail(PURPLE_IS_SAVED_PRESENCE(presence));
+
+	if(!purple_strequal(presence->message, message)) {
+		g_free(presence->message);
+		presence->message = g_strdup(message);
+
+		g_object_notify_by_pspec(G_OBJECT(presence), properties[PROP_MESSAGE]);
+	}
+}
+
+const char *
+purple_saved_presence_get_emoji(PurpleSavedPresence *presence) {
+	g_return_val_if_fail(PURPLE_IS_SAVED_PRESENCE(presence), NULL);
+
+	return presence->emoji;
+}
+
+void
+purple_saved_presence_set_emoji(PurpleSavedPresence *presence,
+                                const char *emoji)
+{
+	g_return_if_fail(PURPLE_IS_SAVED_PRESENCE(presence));
+
+	if(!purple_strequal(presence->emoji, emoji)) {
+		g_free(presence->emoji);
+		presence->emoji = g_strdup(emoji);
+
+		g_object_notify_by_pspec(G_OBJECT(presence), properties[PROP_EMOJI]);
+	}
+}
diff --git a/libpurple/tests/meson.build b/libpurple/tests/meson.build
--- a/libpurple/tests/meson.build
+++ b/libpurple/tests/meson.build
@@ -29,6 +29,7 @@
     'request_field',
     'request_group',
     'request_page',
+    'saved_presence',
     'str',
     'tags',
     'util',
diff --git a/libpurple/tests/test_saved_presence.c b/libpurple/tests/test_saved_presence.c
new file mode 100644
--- /dev/null
+++ b/libpurple/tests/test_saved_presence.c
@@ -0,0 +1,111 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#include <purple.h>
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_purple_saved_presence_new(void) {
+	PurpleSavedPresence *presence = NULL;
+
+	presence = purple_saved_presence_new();
+	g_assert_true(PURPLE_IS_SAVED_PRESENCE(presence));
+
+	g_clear_object(&presence);
+}
+
+static void
+test_purple_saved_presence_properties(void) {
+	PurpleSavedPresence *presence = NULL;
+	PurplePresencePrimitive primitive;
+	GDateTime *last_used = NULL;
+	GDateTime *last_used1 = NULL;
+	guint use_count;
+	char *name;
+	char *message;
+	char *emoji;
+
+	last_used = g_date_time_new_now_local();
+
+	/* Use g_object_new so we can test setting properties by name. All of them
+	 * call the setter methods, so by doing it this way we exercise more of the
+	 * code.
+	 */
+	presence = g_object_new(
+		PURPLE_TYPE_SAVED_PRESENCE,
+		"last-used", last_used,
+		"use-count", 123,
+		"name", "my saved status",
+		"primitive", PURPLE_PRESENCE_PRIMITIVE_STREAMING,
+		"message", "I'm live on twitch at https://twitch.tv/rw_grim/",
+		"emoji", "💀",
+		NULL);
+
+	/* Now use g_object_get to read all of the properties. */
+	g_object_get(
+		presence,
+		"last-used", &last_used1,
+		"use-count", &use_count,
+		"name", &name,
+		"primitive", &primitive,
+		"message", &message,
+		"emoji", &emoji,
+		NULL);
+
+	/* Compare all the things. */
+	g_assert_nonnull(last_used1);
+	g_assert_true(g_date_time_equal(last_used, last_used1));
+	g_clear_pointer(&last_used1, g_date_time_unref);
+
+	g_assert_cmpuint(use_count, ==, 123);
+
+	g_assert_cmpstr(name, ==, "my saved status");
+	g_clear_pointer(&name, g_free);
+
+	g_assert_cmpint(primitive, ==, PURPLE_PRESENCE_PRIMITIVE_STREAMING);
+
+	g_assert_cmpstr(message, ==,
+	                "I'm live on twitch at https://twitch.tv/rw_grim/");
+	g_clear_pointer(&message, g_free);
+
+	g_assert_cmpstr(emoji, ==, "💀");
+	g_clear_pointer(&emoji, g_free);
+
+	g_clear_pointer(&last_used, g_date_time_unref);
+	g_clear_object(&presence);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_add_func("/saved-presence/new",
+	                test_purple_saved_presence_new);
+	g_test_add_func("/saved-presence/properties",
+	                test_purple_saved_presence_properties);
+
+
+	return g_test_run();
+}
