# HG changeset patch
# Node ID 7b6551ae9fd2
# Parent  c4a96b5eecba
diff --git a/libpurple/meson.build b/libpurple/meson.build
--- a/libpurple/meson.build
+++ b/libpurple/meson.build
@@ -41,6 +41,7 @@
 	'purpleaddcontactrequest.c',
 	'purpleattachment.c',
 	'purpleauthorizationrequest.c',
+	'purpleavatar.c',
 	'purplebuddypresence.c',
 	'purplechatconversation.c',
 	'purplechatuser.c',
@@ -145,6 +146,7 @@
 	'purpleaccountusersplit.h',
 	'purpleaddcontactrequest.h',
 	'purpleauthorizationrequest.h',
+	'purpleavatar.h',
 	'purplebuddypresence.h',
 	'purplechatconversation.h',
 	'purplechatuser.h',
diff --git a/libpurple/purpleavatar.h b/libpurple/purpleavatar.h
new file mode 100644
--- /dev/null
+++ b/libpurple/purpleavatar.h
@@ -0,0 +1,151 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * 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 <pidgin.h> may be included directly"
+#endif
+
+#ifndef PURPLE_AVATAR_H
+#define PURPLE_AVATAR_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <libpurple/purpletags.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_AVATAR (purple_avatar_get_type())
+G_DECLARE_FINAL_TYPE(PurpleAvatar, purple_avatar, PURPLE, AVATAR, GObject)
+
+/**
+ * PurpleAvatar:
+ *
+ * A represtation of an Avatar that are used for accounts, contacts, and
+ * conversations.
+ *
+ * Since: 3.0.0
+ */
+
+/**
+ * purple_avatar_new:
+ * @filename: (nullable): The filename for the avatar.
+ * @pixbuf: (nullable): The [class@GdkPixbuf.Pixbuf] to use.
+ *
+ * Creates a new avatar with @filename and @pixbuf.
+ *
+ * Returns: (transfer full): The new instance.
+ *
+ * Since: 3.0.0
+ */
+PurpleAvatar *purple_avatar_new(const char *filename, GdkPixbuf *pixbuf);
+
+/**
+ * purple_avatar_get_filename:
+ * @avatar: The instance.
+ *
+ * Gets the base filename of @avatar.
+ *
+ * Returns: The base filename of @avatar.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_avatar_get_filename(PurpleAvatar *avatar);
+
+/**
+ * purple_avatar_set_filename:
+ * @avatar: The instance.
+ * @filename: The new filename.
+ *
+ * Sets the filename of @avatar to @filename.
+ *
+ * Since: 3.0.0
+ */
+void purple_avatar_set_filename(PurpleAvatar *avatar, const char *filename);
+
+/**
+ * purple_avatar_get_pixbuf:
+ * @avatar: The instance.
+ *
+ * Gets the [class@GdkPixbuf.Pixbuf] of @avatar.
+ *
+ * Returns: The pixbuf of the avatar which could be %NULL.
+ *
+ * Since: 3.0.0
+ */
+GdkPixbuf *purple_avatar_get_pixbuf(PurpleAvatar *avatar);
+
+/**
+ * purple_avatar_set_pixbuf:
+ * @avatar: The instance.
+ * @pixbuf: (nullable): The new [class@GdkPixbuf.Pixbuf].
+ *
+ * Sets the [class@GdkPixbuf.Pixbuf] for @avatar to @pixbuf. If @pixbuf is
+ * %NULL, the pixbuf will be cleared.
+ *
+ * Since: 3.0.0
+ */
+void purple_avatar_set_pixbuf(PurpleAvatar *avatar, GdkPixbuf *pixbuf);
+
+/**
+ * purple_avatar_get_tags:
+ * @avatar: The instance.
+ *
+ * Gets the [class@Purple.Tags] for @avatar.
+ *
+ * Returns: (transfer none): The [class@Purple.Tags] for @avatar.
+ *
+ * Since: 3.0.0
+ */
+PurpleTags *purple_avatar_get_tags(PurpleAvatar *avatar);
+
+/**
+ * purple_avatar_load:
+ * @avatar: The instance.
+ * @error: Return address for a #GError, or %NULL.
+ *
+ * Attempts to load @avatar from disk from [property@Purple.Avatar:filename].
+ * If successful, %TRUE is returned, otherwise %FALSE will be returned with
+ * @error potentially set.
+ *
+ * Returns: %TRUE on success or %FALSE on error with @error potentialy set.
+ *
+ * Since: 3.0.0
+ */
+gboolean purple_avatar_load(PurpleAvatar *avatar, GError **error);
+
+/**
+ * purple_avatar_save:
+ * @avatar: The instance.
+ * @error: Return address for a #GError, or %NULL.
+ *
+ * Attempts to save @avatar to disk using [property@Purple.Avatar:filename].
+ * If successful, %TRUE is returned, otherwise %FALSE will be returned with
+ * @error potentially set.
+ *
+ * Returns: %TRUE on success or %FALSE on error with @error potentialy set.
+ *
+ * Since: 3.0.0
+ */
+gboolean purple_avatar_save(PurpleAvatar *avatar, GError **error);
+
+G_END_DECLS
+
+#endif /* PURPLE_AVATAR_H */
diff --git a/libpurple/purpleavatar.c b/libpurple/purpleavatar.c
new file mode 100644
--- /dev/null
+++ b/libpurple/purpleavatar.c
@@ -0,0 +1,228 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * 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 "purpleavatar.h"
+
+struct _PurpleAvatar {
+	GObject parent;
+
+	char *filename;
+	GdkPixbuf *pixbuf;
+	PurpleTags *tags;
+};
+
+enum {
+	PROP_0,
+	PROP_FILENAME,
+	PROP_PIXBUF,
+	PROP_TAGS,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+G_DEFINE_TYPE(PurpleAvatar, purple_avatar, G_TYPE_OBJECT)
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+purple_avatar_get_property(GObject *obj, guint param_id, GValue *value,
+                           GParamSpec *pspec)
+{
+	PurpleAvatar *avatar = PURPLE_AVATAR(obj);
+
+	switch(param_id) {
+		case PROP_FILENAME:
+			g_value_set_string(value, purple_avatar_get_filename(avatar));
+			break;
+		case PROP_PIXBUF:
+			g_value_set_object(value, purple_avatar_get_pixbuf(avatar));
+			break;
+		case PROP_TAGS:
+			g_value_set_object(value, purple_avatar_get_tags(avatar));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_avatar_set_property(GObject *obj, guint param_id, const GValue *value,
+                           GParamSpec *pspec)
+{
+	PurpleAvatar *avatar = PURPLE_AVATAR(obj);
+
+	switch(param_id) {
+		case PROP_FILENAME:
+			purple_avatar_set_filename(avatar, g_value_get_string(value));
+			break;
+		case PROP_PIXBUF:
+			purple_avatar_set_pixbuf(avatar, g_value_get_object(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_avatar_finalize(GObject *obj) {
+	PurpleAvatar *avatar = PURPLE_AVATAR(obj);
+
+	g_clear_pointer(&avatar->filename, g_free);
+	g_clear_object(&avatar->pixbuf);
+	g_clear_object(&avatar->tags);
+
+	G_OBJECT_CLASS(purple_avatar_parent_class)->finalize(obj);
+}
+
+static void
+purple_avatar_init(PurpleAvatar *avatar) {
+	avatar->tags = purple_tags_new();
+}
+
+static void
+purple_avatar_class_init(PurpleAvatarClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->finalize = purple_avatar_finalize;
+	obj_class->get_property = purple_avatar_get_property;
+	obj_class->set_property = purple_avatar_set_property;
+
+	/**
+	 * PurpleAvatar:filename:
+	 *
+	 * The filename to save/load the avatar to/from.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_FILENAME] = g_param_spec_string(
+		"filename", "filename",
+		"The filename to save/load the avatar from.",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleAvatar:pixbuf:
+	 *
+	 * The [class@GdkPixbuf.Pixbuf]. This may be %NULL if
+	 * [method@Purple.Avatar.load] has not yet been called.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_PIXBUF] = g_param_spec_object(
+		"pixbuf", "pixbuf",
+		"The pixbuf of the avatar.",
+		GDK_TYPE_PIXBUF,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleAvatar:tags:
+	 *
+	 * The [class@Purple.Tags] for the avatar.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_TAGS] = g_param_spec_object(
+		"tags", "tags",
+		"The tags for the avatar.",
+		PURPLE_TYPE_TAGS,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+PurpleAvatar *
+purple_avatar_new(const char *filename, GdkPixbuf *pixbuf) {
+	return g_object_new(
+		PURPLE_TYPE_AVATAR,
+		"filename", filename,
+		"pixbuf", pixbuf,
+		NULL);
+}
+
+const char *
+purple_avatar_get_filename(PurpleAvatar *avatar) {
+	g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), NULL);
+
+	return avatar->filename;
+}
+
+void
+purple_avatar_set_filename(PurpleAvatar *avatar, const char *filename) {
+	g_return_if_fail(PURPLE_IS_AVATAR(avatar));
+
+	g_free(avatar->filename);
+	avatar->filename = g_strdup(filename);
+
+	g_object_notify_by_pspec(G_OBJECT(avatar), properties[PROP_FILENAME]);
+}
+
+GdkPixbuf *
+purple_avatar_get_pixbuf(PurpleAvatar *avatar) {
+	g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), NULL);
+
+	return avatar->pixbuf;
+}
+
+void
+purple_avatar_set_pixbuf(PurpleAvatar *avatar, GdkPixbuf *pixbuf) {
+	g_return_if_fail(PURPLE_IS_AVATAR(avatar));
+
+	if(g_set_object(&avatar->pixbuf, pixbuf)) {
+		g_object_notify_by_pspec(G_OBJECT(avatar), properties[PROP_PIXBUF]);
+	}
+}
+
+PurpleTags *
+purple_avatar_get_tags(PurpleAvatar *avatar) {
+	g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), NULL);
+
+	return avatar->tags;
+}
+
+gboolean
+purple_avatar_load(PurpleAvatar *avatar, GError **error) {
+	GError *local_error = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), FALSE);
+
+	g_clear_object(&avatar->pixbuf);
+
+	avatar->pixbuf = gdk_pixbuf_new_from_file(avatar->filename, &local_error);
+
+	if(avatar->pixbuf == NULL) {
+		g_propagate_error(error, local_error);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean
+purple_avatar_save(PurpleAvatar *avatar, GError **error) {
+	g_return_val_if_fail(PURPLE_IS_AVATAR(avatar), FALSE);
+
+	return gdk_pixbuf_save(avatar->pixbuf, avatar->filename, "png", error,
+	                       "quality", "100", NULL);
+}
diff --git a/libpurple/tests/meson.build b/libpurple/tests/meson.build
--- a/libpurple/tests/meson.build
+++ b/libpurple/tests/meson.build
@@ -2,6 +2,7 @@
     'account_option',
     'account_manager',
     'authorization_request',
+    'avatar',
     'circular_buffer',
     'contact',
     'contact_manager',
diff --git a/libpurple/tests/test_avatar.c b/libpurple/tests/test_avatar.c
new file mode 100644
--- /dev/null
+++ b/libpurple/tests/test_avatar.c
@@ -0,0 +1,97 @@
+/*
+ * 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_avatar_new(void) {
+	PurpleAvatar *avatar = NULL;
+	GdkPixbuf *pixbuf = NULL;
+
+	pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
+	avatar = purple_avatar_new("filename", pixbuf);
+
+	g_assert_true(PURPLE_IS_AVATAR(avatar));
+
+	g_assert_cmpstr(purple_avatar_get_filename(avatar), ==, "filename");
+	g_assert_true(purple_avatar_get_pixbuf(avatar) == pixbuf);
+
+	g_clear_object(&avatar);
+	g_clear_object(&pixbuf);
+}
+
+static void
+test_purple_avatar_properties(void) {
+	PurpleAvatar *avatar = NULL;
+	PurpleTags *tags = NULL;
+	GdkPixbuf *pixbuf = NULL;
+	GdkPixbuf *pixbuf1 = NULL;
+	gchar *filename = NULL;
+
+	pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
+
+	/* 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.
+	 */
+	avatar = g_object_new(
+		PURPLE_TYPE_AVATAR,
+		"filename", "filename",
+		"pixbuf", pixbuf,
+		NULL);
+
+	/* Now use g_object_get to read all of the properties. */
+	g_object_get(avatar,
+		"filename", &filename,
+		"pixbuf", &pixbuf1,
+		"tags", &tags,
+		NULL);
+
+	/* Compare all the things. */
+	g_assert_cmpstr(filename, ==, "filename");
+	g_assert_true(pixbuf1 == pixbuf);
+	g_assert_nonnull(tags);
+
+	/* Free/unref all the things. */
+	g_clear_pointer(&filename, g_free);
+	g_clear_object(&pixbuf1);
+	g_clear_object(&tags);
+
+	g_clear_object(&avatar);
+	g_clear_object(&pixbuf);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_add_func("/avatar/new",
+	                test_purple_avatar_new);
+	g_test_add_func("/avatar/properties",
+	                test_purple_avatar_properties);
+
+	return g_test_run();
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -167,6 +167,7 @@
 libpurple/purpleaccountusersplit.c
 libpurple/purpleaddcontactrequest.c
 libpurple/purpleattachment.c
+libpurple/purpleavatar.c
 libpurple/purplebuddypresence.c
 libpurple/purplechatconversation.c
 libpurple/purplechatuser.c
