# HG changeset patch
# Node ID fcb3a3201fb56d981b216b29c3e1cb4885453ae1
# Parent  b6cb116ce0638f834060a4c199538d5796fce495
diff --git a/libpurple/purpleprotocolmanager.c b/libpurple/purpleprotocolmanager.c
--- a/libpurple/purpleprotocolmanager.c
+++ b/libpurple/purpleprotocolmanager.c
@@ -25,6 +25,8 @@
 #include "purpleprotocolmanager.h"
 #include "purpleprotocolmanagerprivate.h"
 
+#include "util.h"
+
 enum {
 	PROP_0,
 	PROP_ITEM_TYPE,
@@ -43,13 +45,40 @@
 struct _PurpleProtocolManager {
 	GObject parent;
 
-	GHashTable *protocols;
-	GPtrArray *list;
+	GPtrArray *protocols;
 };
 
 static PurpleProtocolManager *default_manager = NULL;
 
 /******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static PurpleProtocol *
+purple_protocol_manager_find_with_id(PurpleProtocolManager *manager,
+                                     const char *id, guint *position)
+{
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_MANAGER(manager), NULL);
+
+	for(guint i = 0; i < manager->protocols->len; i++) {
+		PurpleProtocol *protocol = NULL;
+		const char *candidate_id = NULL;
+
+		protocol = g_ptr_array_index(manager->protocols, i);
+		candidate_id = purple_protocol_get_id(protocol);
+
+		if(purple_strequal(candidate_id, id)) {
+			if(position != NULL) {
+				*position = i;
+			}
+
+			return protocol;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************************
  * GListModel Implementation
  *****************************************************************************/
 static GType
@@ -61,7 +90,7 @@
 purple_protocol_manager_get_n_items(GListModel *list) {
 	PurpleProtocolManager *manager = PURPLE_PROTOCOL_MANAGER(list);
 
-	return manager->list->len;
+	return manager->protocols->len;
 }
 
 static gpointer
@@ -69,8 +98,9 @@
 	PurpleProtocolManager *manager = PURPLE_PROTOCOL_MANAGER(list);
 	PurpleProtocol *protocol = NULL;
 
-	if(position < manager->list->len) {
-		protocol = g_object_ref(g_ptr_array_index(manager->list, position));
+	if(position < manager->protocols->len) {
+		protocol = g_object_ref(g_ptr_array_index(manager->protocols,
+		                                          position));
 	}
 
 	return protocol;
@@ -97,8 +127,7 @@
 
 	manager = PURPLE_PROTOCOL_MANAGER(obj);
 
-	g_clear_pointer(&manager->protocols, g_hash_table_destroy);
-	g_clear_pointer(&manager->list, g_ptr_array_unref);
+	g_clear_pointer(&manager->protocols, g_ptr_array_unref);
 
 	G_OBJECT_CLASS(purple_protocol_manager_parent_class)->finalize(obj);
 }
@@ -107,16 +136,16 @@
 purple_protocol_manager_get_property(GObject *obj, guint param_id,
                                      GValue *value, GParamSpec *pspec)
 {
-	PurpleProtocolManager *manager = PURPLE_PROTOCOL_MANAGER(obj);
+	GListModel *model = G_LIST_MODEL(obj);
 
 	switch(param_id) {
 	case PROP_ITEM_TYPE:
 		g_value_set_gtype(value,
-		                  purple_protocol_manager_get_item_type(G_LIST_MODEL(manager)));
+		                  purple_protocol_manager_get_item_type(model));
 		break;
 	case PROP_N_ITEMS:
 		g_value_set_uint(value,
-		                 purple_protocol_manager_get_n_items(G_LIST_MODEL(manager)));
+		                 purple_protocol_manager_get_n_items(model));
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
@@ -126,9 +155,7 @@
 
 static void
 purple_protocol_manager_init(PurpleProtocolManager *manager) {
-	manager->protocols = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
-	                                           g_object_unref);
-	manager->list = g_ptr_array_new();
+	manager->protocols = g_ptr_array_new();
 }
 
 static void
@@ -216,6 +243,8 @@
 purple_protocol_manager_startup(void) {
 	if(default_manager == NULL) {
 		default_manager = g_object_new(PURPLE_TYPE_PROTOCOL_MANAGER, NULL);
+		g_object_add_weak_pointer(G_OBJECT(default_manager),
+		                          (gpointer)&default_manager);
 	}
 }
 
@@ -231,25 +260,27 @@
 purple_protocol_manager_add(PurpleProtocolManager *manager,
                             PurpleProtocol *protocol, GError **error)
 {
-	const gchar *id = NULL;
+	PurpleProtocol *candidate = NULL;
+	const char *id = NULL;
+	guint position = 0;
 
 	g_return_val_if_fail(PURPLE_IS_PROTOCOL_MANAGER(manager), FALSE);
 	g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), FALSE);
 
 	id = purple_protocol_get_id(protocol);
-	if(g_hash_table_lookup(manager->protocols, id) != NULL) {
+	candidate = purple_protocol_manager_find_with_id(manager, id, NULL);
+	if(PURPLE_IS_PROTOCOL(candidate)) {
 		g_set_error(error, PURPLE_PROTOCOL_MANAGER_DOMAIN, 0,
 		            _("protocol %s has already been added"), id);
 
 		return FALSE;
 	}
 
-	g_hash_table_insert(manager->protocols, g_strdup(id),
-	                    g_object_ref(protocol));
-	g_ptr_array_add(manager->list, protocol);
-	g_list_model_items_changed(G_LIST_MODEL(manager), manager->list->len - 1, 0, 1);
+	position = manager->protocols->len;
+	g_ptr_array_add(manager->protocols, g_object_ref(protocol));
 
 	g_signal_emit(G_OBJECT(manager), signals[SIG_ADDED], 0, protocol);
+	g_list_model_items_changed(G_LIST_MODEL(manager), position, 0, 1);
 
 	return TRUE;
 }
@@ -260,26 +291,21 @@
 }
 
 PurpleProtocol *
-purple_protocol_manager_find(PurpleProtocolManager *manager, const gchar *id) {
-	gpointer value = NULL;
-
+purple_protocol_manager_find(PurpleProtocolManager *manager, const char *id) {
 	g_return_val_if_fail(PURPLE_IS_PROTOCOL_MANAGER(manager), NULL);
-	g_return_val_if_fail(id != NULL, NULL);
+	g_return_val_if_fail(!purple_strempty(id), NULL);
 
-	value = g_hash_table_lookup(manager->protocols, id);
-	if(value == NULL) {
-		return NULL;
-	}
-
-	return PURPLE_PROTOCOL(value);
+	return purple_protocol_manager_find_with_id(manager, id, NULL);
 }
 
 gboolean
 purple_protocol_manager_remove(PurpleProtocolManager *manager,
                                PurpleProtocol *protocol, GError **error)
 {
-	const gchar *id = NULL;
+	PurpleProtocol *candidate = NULL;
+	const char *id = NULL;
 	gboolean ret = FALSE;
+	guint position = 0;
 
 	g_return_val_if_fail(PURPLE_IS_PROTOCOL_MANAGER(manager), FALSE);
 	g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), FALSE);
@@ -292,16 +318,12 @@
 	g_object_ref(protocol);
 
 	id = purple_protocol_get_id(protocol);
-
-	if(g_hash_table_remove(manager->protocols, id)) {
-		guint position;
-
-		if(g_ptr_array_find(manager->list, protocol, &position)) {
-			g_ptr_array_remove_index(manager->list, position);
-			g_list_model_items_changed(G_LIST_MODEL(manager), position, 1, 0);
-		}
+	candidate = purple_protocol_manager_find_with_id(manager, id, &position);
+	if(PURPLE_IS_PROTOCOL(candidate)) {
+		g_ptr_array_remove_index(manager->protocols, position);
 
 		g_signal_emit(G_OBJECT(manager), signals[SIG_REMOVED], 0, protocol);
+		g_list_model_items_changed(G_LIST_MODEL(manager), position, 1, 0);
 
 		ret = TRUE;
 	} else {
diff --git a/libpurple/tests/meson.build b/libpurple/tests/meson.build
--- a/libpurple/tests/meson.build
+++ b/libpurple/tests/meson.build
@@ -40,6 +40,7 @@
     'protocol_contacts',
     'protocol_conversation',
     'protocol_file_transfer',
+    'protocol_manager',
     'protocol_roster',
     'purplepath',
     'request_field',
diff --git a/libpurple/tests/test_protocol_manager.c b/libpurple/tests/test_protocol_manager.c
new file mode 100644
--- /dev/null
+++ b/libpurple/tests/test_protocol_manager.c
@@ -0,0 +1,75 @@
+/*
+ * 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_protocol_manager_new(void) {
+	PurpleProtocolManager *manager = NULL;
+
+	manager = g_object_new(PURPLE_TYPE_PROTOCOL_MANAGER, NULL);
+
+	g_assert_true(PURPLE_IS_PROTOCOL_MANAGER(manager));
+	g_assert_true(G_IS_LIST_MODEL(manager));
+
+	g_assert_finalize_object(manager);
+}
+
+static void
+test_purple_protocol_manager_properties(void) {
+	PurpleProtocolManager *manager = NULL;
+	GType item_type = G_TYPE_INVALID;
+	guint n_items = 0;
+
+	manager = g_object_new(PURPLE_TYPE_PROTOCOL_MANAGER, NULL);
+
+	g_object_get(
+		G_OBJECT(manager),
+		"item-type", &item_type,
+		"n-items", &n_items,
+		NULL);
+
+	g_assert_true(item_type == PURPLE_TYPE_PROTOCOL);
+
+	/* While there aren't any items here, this just helps us verify that the
+	 * property exists.
+	 */
+	g_assert_cmpuint(n_items, ==, 0);
+
+	g_assert_finalize_object(manager);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+int
+main(int argc, char *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+	g_test_set_nonfatal_assertions();
+
+	g_test_add_func("/protocol-manager/new", test_purple_protocol_manager_new);
+	g_test_add_func("/protocol-manager/properties",
+	                test_purple_protocol_manager_properties);
+
+	return g_test_run();
+}
