# HG changeset patch
# Node ID 4f6e34125b19cfded1fdc5f73e20db5b30c7a161
# Parent  b97e5e85b97d64d8e08caf2d34eefdcf33869d17
diff --git a/libpurple/meson.build b/libpurple/meson.build
--- a/libpurple/meson.build
+++ b/libpurple/meson.build
@@ -17,6 +17,7 @@
 	'purpleauthorizationrequest.c',
 	'purpleavatar.c',
 	'purplebadge.c',
+	'purplebadgemanager.c',
 	'purplebadges.c',
 	'purplechanneljoindetails.c',
 	'purplecommand.c',
@@ -110,6 +111,7 @@
 	'purpleauthorizationrequest.h',
 	'purpleavatar.h',
 	'purplebadge.h',
+	'purplebadgemanager.h',
 	'purplebadges.h',
 	'purplechanneljoindetails.h',
 	'purplecommand.h',
diff --git a/libpurple/purplebadgemanager.c b/libpurple/purplebadgemanager.c
new file mode 100644
--- /dev/null
+++ b/libpurple/purplebadgemanager.c
@@ -0,0 +1,113 @@
+/*
+ * 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 library 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 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 General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "purplebadgemanager.h"
+
+#include "util.h"
+
+struct _PurpleBadgeManager {
+	GObject parent;
+
+	GHashTable *badges;
+};
+
+static PurpleBadgeManager *default_manager = NULL;
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_FINAL_TYPE(PurpleBadgeManager, purple_badge_manager, G_TYPE_OBJECT)
+
+static void
+purple_badge_manager_finalize(GObject *obj) {
+	PurpleBadgeManager *manager = PURPLE_BADGE_MANAGER(obj);
+
+	g_clear_pointer(&manager->badges, g_hash_table_unref);
+
+	G_OBJECT_CLASS(purple_badge_manager_parent_class)->finalize(obj);
+}
+
+static void
+purple_badge_manager_init(PurpleBadgeManager *manager) {
+	manager->badges = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+	                                        g_object_unref);
+}
+
+static void
+purple_badge_manager_class_init(PurpleBadgeManagerClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->finalize = purple_badge_manager_finalize;
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+gboolean
+purple_badge_manager_add(PurpleBadgeManager *manager, PurpleBadge *badge) {
+	const char *id = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_BADGE_MANAGER(manager), FALSE);
+	g_return_val_if_fail(PURPLE_IS_BADGE(badge), FALSE);
+
+	id = purple_badge_get_id(badge);
+	if(purple_strempty(id)) {
+		g_warning("attempted to add a badge with no id to a badge manager");
+
+		return FALSE;
+	}
+
+	return g_hash_table_insert(manager->badges, g_strdup(id),
+	                           g_object_ref(G_OBJECT(badge)));
+}
+
+PurpleBadgeManager *
+purple_badge_manager_new(void) {
+	return g_object_new(PURPLE_TYPE_BADGE_MANAGER, NULL);
+}
+
+PurpleBadgeManager *
+purple_badge_manager_get_default(void) {
+	if(!PURPLE_IS_BADGE_MANAGER(default_manager)) {
+		default_manager = purple_badge_manager_new();
+		g_object_add_weak_pointer(G_OBJECT(default_manager),
+		                          (gpointer *)&default_manager);
+	}
+
+	return default_manager;
+}
+
+PurpleBadge *
+purple_badge_manager_find(PurpleBadgeManager *manager, const char *id) {
+	g_return_val_if_fail(PURPLE_IS_BADGE_MANAGER(manager), NULL);
+	g_return_val_if_fail(!purple_strempty(id), NULL);
+
+	return g_hash_table_lookup(manager->badges, id);
+}
+
+gboolean
+purple_badge_manager_remove(PurpleBadgeManager *manager, const char *id) {
+	g_return_val_if_fail(PURPLE_IS_BADGE_MANAGER(manager), FALSE);
+	g_return_val_if_fail(!purple_strempty(id), FALSE);
+
+	return g_hash_table_remove(manager->badges, id);
+}
diff --git a/libpurple/purplebadgemanager.h b/libpurple/purplebadgemanager.h
new file mode 100644
--- /dev/null
+++ b/libpurple/purplebadgemanager.h
@@ -0,0 +1,127 @@
+/*
+ * 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 library 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 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 General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this library; 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_BADGE_MANAGER_H
+#define PURPLE_BADGE_MANAGER_H
+
+#include <glib.h>
+
+#include "purplebadge.h"
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_BADGE_MANAGER (purple_badge_manager_get_type())
+
+/**
+ * PurpleBadgeManager:
+ *
+ * A cache for badges.
+ *
+ * This can be used to reuse badges where necessary without having to create a
+ * new instance every time.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+G_DECLARE_FINAL_TYPE(PurpleBadgeManager, purple_badge_manager,
+                     PURPLE, BADGE_MANAGER, GObject)
+
+/**
+ * purple_badge_manager_add:
+ * @manager: The instance.
+ * @badge: The badge to add.
+ *
+ * Adds @badge to @manager.
+ *
+ * Returns: %TRUE if @badge was not already added to @manager.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_badge_manager_add(PurpleBadgeManager *manager, PurpleBadge *badge);
+
+/**
+ * purple_badge_manager_new:
+ *
+ * Creates a new badge manager.
+ *
+ * A badge manager is just a runtime cache to help protocols and plugins by
+ * holding onto an instance which can be referenced instead of having to always
+ * create a new instance.
+ *
+ * Returns: (transfer full): The new instance.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+PurpleBadgeManager *purple_badge_manager_new(void);
+
+/**
+ * purple_badge_manager_get_default:
+ *
+ * Gets the default instance of #PurpleBadgeManager.
+ *
+ * This instance can be used for any of the API including connecting to
+ * signals.
+ *
+ * Returns: (transfer none): The default instance.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+PurpleBadgeManager *purple_badge_manager_get_default(void);
+
+/**
+ * purple_badge_manager_find:
+ * @manager: The instance.
+ * @id: The identifier of the badge.
+ *
+ * Looks for badge with the given id.
+ *
+ * Returns: (transfer none) (nullable): The [class@PurpleBadge] if found.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+PurpleBadge *purple_badge_manager_find(PurpleBadgeManager *manager, const char *id);
+
+/**
+ * purple_badge_manager_remove:
+ * @manager: The instance.
+ * @id: The identifier of the badge.
+ *
+ * Removes a badge with the given identifier.
+ *
+ * Returns: %TRUE if @badge was found and removed.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_badge_manager_remove(PurpleBadgeManager *manager, const char *id);
+
+G_END_DECLS
+
+#endif /* PURPLE_BADGE_MANAGER_H */
diff --git a/libpurple/tests/meson.build b/libpurple/tests/meson.build
--- a/libpurple/tests/meson.build
+++ b/libpurple/tests/meson.build
@@ -4,6 +4,7 @@
     'account_manager',
     'authorization_request',
     'badge',
+    'badge_manager',
     'badges',
     'channel_join_details',
     'circular_buffer',
diff --git a/libpurple/tests/test_badge_manager.c b/libpurple/tests/test_badge_manager.c
new file mode 100644
--- /dev/null
+++ b/libpurple/tests/test_badge_manager.c
@@ -0,0 +1,89 @@
+/*
+ * 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_badge_manager_new(void) {
+	PurpleBadgeManager *manager = NULL;
+
+	manager = purple_badge_manager_new();
+
+	g_assert_true(PURPLE_IS_BADGE_MANAGER(manager));
+
+	g_assert_finalize_object(manager);
+}
+
+static void
+test_purple_badge_manager_add_remove(void) {
+	PurpleBadge *badge = NULL;
+	PurpleBadgeManager *manager = NULL;
+	gboolean ret = FALSE;
+	const char *id = "test-badge";
+
+	manager = purple_badge_manager_new();
+
+	/* Make sure we can't remove our badge as it hasn't been added yet. */
+	ret = purple_badge_manager_remove(manager, id);
+	g_assert_false(ret);
+
+	/* Also make sure we can't find out badge. */
+	badge = purple_badge_manager_find(manager, id);
+	g_assert_null(badge);
+
+	/* Create and add our badge. */
+	badge = purple_badge_new(id, 0, "test-badge", "T");
+	ret = purple_badge_manager_add(manager, badge);
+	g_assert_true(ret);
+
+	/* Add the badge again and verify that it returns false. */
+	ret = purple_badge_manager_add(manager, badge);
+	g_assert_false(ret);
+
+	/* Remove the badge. */
+	ret = purple_badge_manager_remove(manager, id);
+	g_assert_true(ret);
+
+	/* Make sure that another remove returns false. */
+	ret = purple_badge_manager_remove(manager, id);
+	g_assert_false(ret);
+
+	g_assert_finalize_object(manager);
+	g_assert_finalize_object(badge);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+int
+main(int argc, char *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+	g_test_set_nonfatal_assertions();
+
+	g_test_add_func("/conversation-manager/new",
+	                test_purple_badge_manager_new);
+	g_test_add_func("/conversation-manager/add-remove",
+	                test_purple_badge_manager_add_remove);
+
+	return g_test_run();
+}
