# HG changeset patch
# Node ID ee2f3294ef6e4f3c96dd8f3cb3a21662cf7feb8e
# Parent  b6cb116ce0638f834060a4c199538d5796fce495
diff --git a/libpurple/purplecredentialmanager.c b/libpurple/purplecredentialmanager.c
--- a/libpurple/purplecredentialmanager.c
+++ b/libpurple/purplecredentialmanager.c
@@ -35,6 +35,14 @@
 #include "util.h"
 
 enum {
+	PROP_0,
+	PROP_ITEM_TYPE,
+	PROP_N_ITEMS,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+enum {
 	SIG_ADDED,
 	SIG_REMOVED,
 	SIG_ACTIVE_CHANGED,
@@ -45,17 +53,43 @@
 struct _PurpleCredentialManager {
 	GObject parent;
 
-	GHashTable *providers;
+	GPtrArray *providers;
 
 	PurpleCredentialProvider *active;
 
 	GSettings *settings;
 };
 
-G_DEFINE_FINAL_TYPE(PurpleCredentialManager, purple_credential_manager,
-                    G_TYPE_OBJECT);
+static PurpleCredentialManager *default_manager = NULL;
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static PurpleCredentialProvider *
+purple_credential_manager_find_provider_with_id(PurpleCredentialManager *manager,
+                                                const char *id,
+                                                guint *position)
+{
+	g_return_val_if_fail(PURPLE_IS_CREDENTIAL_MANAGER(manager), NULL);
 
-static PurpleCredentialManager *default_manager = NULL;
+	for(guint i = 0; i < manager->providers->len; i++) {
+		PurpleCredentialProvider *provider = NULL;
+		const char *provider_id = NULL;
+
+		provider = g_ptr_array_index(manager->providers, i);
+		provider_id = purple_credential_provider_get_id(provider);
+
+		if(purple_strequal(provider_id, id)) {
+			if(position != NULL) {
+				*position = i;
+			}
+
+			return provider;
+		}
+	}
+
+	return NULL;
+}
 
 /******************************************************************************
  * Async Callbacks
@@ -170,15 +204,56 @@
 }
 
 /******************************************************************************
+ * GListModel Implementation
+ *****************************************************************************/
+static GType
+purple_credential_manager_get_item_type(G_GNUC_UNUSED GListModel *list) {
+	return PURPLE_TYPE_CREDENTIAL_PROVIDER;
+}
+
+static guint
+purple_credential_manager_get_n_items(GListModel *list) {
+	PurpleCredentialManager *manager = PURPLE_CREDENTIAL_MANAGER(list);
+
+	return manager->providers->len;
+}
+
+static gpointer
+purple_credential_manager_get_item(GListModel *list, guint position) {
+	PurpleCredentialManager *manager = PURPLE_CREDENTIAL_MANAGER(list);
+	PurpleCredentialProvider *provider = NULL;
+
+	if(position < manager->providers->len) {
+		provider = g_object_ref(g_ptr_array_index(manager->providers,
+		                                          position));
+	}
+
+	return provider;
+}
+
+static void
+pidgin_credential_manager_list_model_iface_init(GListModelInterface *iface) {
+	iface->get_item_type = purple_credential_manager_get_item_type;
+	iface->get_n_items = purple_credential_manager_get_n_items;
+	iface->get_item = purple_credential_manager_get_item;
+}
+
+/******************************************************************************
  * GObject Implementation
  *****************************************************************************/
+G_DEFINE_FINAL_TYPE_WITH_CODE(PurpleCredentialManager,
+                              purple_credential_manager,
+                              G_TYPE_OBJECT,
+                              G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL,
+                                                    pidgin_credential_manager_list_model_iface_init))
+
 static void
 purple_credential_manager_finalize(GObject *obj) {
 	PurpleCredentialManager *manager = NULL;
 
 	manager = PURPLE_CREDENTIAL_MANAGER(obj);
 
-	g_clear_pointer(&manager->providers, g_hash_table_destroy);
+	g_clear_pointer(&manager->providers, g_ptr_array_unref);
 	g_clear_object(&manager->active);
 	g_clear_object(&manager->settings);
 
@@ -186,9 +261,29 @@
 }
 
 static void
+purple_credential_manager_get_property(GObject *obj, guint param_id,
+                                       GValue *value, GParamSpec *pspec)
+{
+	GListModel *model = G_LIST_MODEL(obj);
+
+	switch(param_id) {
+	case PROP_ITEM_TYPE:
+		g_value_set_gtype(value,
+		                  purple_credential_manager_get_item_type(model));
+		break;
+	case PROP_N_ITEMS:
+		g_value_set_uint(value,
+		                 purple_credential_manager_get_n_items(model));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
 purple_credential_manager_init(PurpleCredentialManager *manager) {
-	manager->providers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
-	                                           g_object_unref);
+	manager->providers = g_ptr_array_new_full(1, g_object_unref);
 }
 
 static void
@@ -196,6 +291,35 @@
 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
 
 	obj_class->finalize = purple_credential_manager_finalize;
+	obj_class->get_property = purple_credential_manager_get_property;
+
+	/**
+	 * PurpleCredentialManager:item-type:
+	 *
+	 * The type of items. See [vfunc@Gio.ListModel.get_item_type].
+	 *
+	 * Since: 3.0
+	 */
+	properties[PROP_ITEM_TYPE] = g_param_spec_gtype(
+		"item-type", "item-type",
+		"The type of the contained items.",
+		G_TYPE_OBJECT,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleCredentialManager:n-items:
+	 *
+	 * The number of items. See [vfunc@Gio.ListModel.get_n_items].
+	 *
+	 * Since: 3.0
+	 */
+	properties[PROP_N_ITEMS] = g_param_spec_uint(
+		"n-items", "n-items",
+		"The number of contained items.",
+		0, G_MAXUINT, 0,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
 
 	/**
 	 * PurpleCredentialManager::added:
@@ -304,8 +428,6 @@
 void
 purple_credential_manager_shutdown(G_GNUC_UNUSED PurpleCore *core) {
 	if(PURPLE_IS_CREDENTIAL_MANAGER(default_manager)) {
-		guint size = 0;
-
 		/* If we have an instance of the noop provider we need to unregister
 		 * it before continuing.
 		 */
@@ -321,11 +443,10 @@
 			g_clear_object(&noop);
 		}
 
-		size = g_hash_table_size(default_manager->providers);
-		if(size > 0) {
-			g_warning("purple_credential_manager_shutdown called while %d "
-				      "providers were still registered. Skipping shutdown",
-				      size);
+		if(default_manager->providers->len > 0) {
+			g_warning("purple_credential_manager_shutdown called while %u "
+			          "providers were still registered. Skipping shutdown",
+			          default_manager->providers->len);
 		} else {
 			g_clear_object(&default_manager);
 		}
@@ -340,7 +461,9 @@
                               PurpleCredentialProvider *provider,
                               GError **error)
 {
+	PurpleCredentialProvider *existing = NULL;
 	const char *id = NULL;
+	guint position = 0;
 
 	g_return_val_if_fail(PURPLE_IS_CREDENTIAL_MANAGER(manager), FALSE);
 	g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
@@ -352,17 +475,21 @@
 	}
 
 	id = purple_credential_provider_get_id(provider);
-	if(g_hash_table_lookup(manager->providers, id) != NULL) {
+	existing = purple_credential_manager_find_provider_with_id(manager, id,
+	                                                           NULL);
+	if(PURPLE_IS_CREDENTIAL_PROVIDER(existing)) {
 		g_set_error(error, PURPLE_CREDENTIAL_MANAGER_DOMAIN, 0,
 		            _("provider %s has already been added"), id);
 
 		return FALSE;
 	}
 
-	g_hash_table_insert(manager->providers, g_strdup(id),
-	                    g_object_ref(provider));
+	/* Store the position to avoid doing math after adding. */
+	position = manager->providers->len;
+	g_ptr_array_add(manager->providers, g_object_ref(provider));
 
 	g_signal_emit(G_OBJECT(manager), signals[SIG_ADDED], 0, provider);
+	g_list_model_items_changed(G_LIST_MODEL(manager), position, 0, 1);
 
 	/* If we don't currently have an active provider, check if the newly
 	 * registered provider has the id of the stored provider in preferences.
@@ -390,17 +517,28 @@
 	return default_manager;
 }
 
+GListModel *
+purple_credential_manager_get_default_as_model(void) {
+	if(PURPLE_IS_CREDENTIAL_MANAGER(default_manager)) {
+		return G_LIST_MODEL(default_manager);
+	}
+
+	return NULL;
+}
+
 gboolean
 purple_credential_manager_set_active(PurpleCredentialManager *manager,
                                      const char *id, GError **error)
 {
-	PurpleCredentialProvider *previous = NULL, *provider = NULL;
+	PurpleCredentialProvider *previous = NULL;
+	PurpleCredentialProvider *provider = NULL;
 
 	g_return_val_if_fail(PURPLE_IS_CREDENTIAL_MANAGER(manager), FALSE);
 
 	/* First look up the new provider if we're given one. */
 	if(id != NULL) {
-		provider = g_hash_table_lookup(manager->providers, id);
+		provider = purple_credential_manager_find_provider_with_id(manager, id,
+		                                                           NULL);
 		if(!PURPLE_IS_CREDENTIAL_PROVIDER(provider)) {
 			g_set_error(error, PURPLE_CREDENTIAL_MANAGER_DOMAIN, 0,
 			            "no credential provider found with id %s", id);
@@ -587,15 +725,14 @@
                                   PurpleCredentialManagerForeachFunc func,
                                   gpointer data)
 {
-	GHashTableIter iter;
-	gpointer value;
-
 	g_return_if_fail(PURPLE_IS_CREDENTIAL_MANAGER(manager));
 	g_return_if_fail(func != NULL);
 
-	g_hash_table_iter_init(&iter, manager->providers);
-	while(g_hash_table_iter_next(&iter, NULL, &value)) {
-		func(PURPLE_CREDENTIAL_PROVIDER(value), data);
+	for(guint i = 0; i < manager->providers->len; i++) {
+		PurpleCredentialProvider *provider = NULL;
+
+		provider = g_ptr_array_index(manager->providers, i);
+		func(provider, data);
 	}
 }
 
@@ -604,7 +741,9 @@
                                  PurpleCredentialProvider *provider,
                                  GError **error)
 {
+	PurpleCredentialProvider *candidate = NULL;
 	const char *id = NULL;
+	guint position = 0;
 
 	g_return_val_if_fail(PURPLE_IS_CREDENTIAL_MANAGER(manager), FALSE);
 	g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
@@ -618,8 +757,16 @@
 		return FALSE;
 	}
 
-	if(g_hash_table_remove(manager->providers, id)) {
+	g_object_ref(provider);
+	candidate = purple_credential_manager_find_provider_with_id(manager, id,
+	                                                            &position);
+	if(PURPLE_IS_CREDENTIAL_PROVIDER(candidate)) {
+		g_ptr_array_remove_index(manager->providers, position);
+
 		g_signal_emit(G_OBJECT(manager), signals[SIG_REMOVED], 0, provider);
+		g_list_model_items_changed(G_LIST_MODEL(manager), position, 1, 0);
+
+		g_object_unref(provider);
 
 		return TRUE;
 	}
@@ -627,5 +774,7 @@
 	g_set_error(error, PURPLE_CREDENTIAL_MANAGER_DOMAIN, 0,
 	            _("provider %s has not been added"), id);
 
+	g_object_unref(provider);
+
 	return FALSE;
 }
diff --git a/libpurple/purplecredentialmanager.h b/libpurple/purplecredentialmanager.h
--- a/libpurple/purplecredentialmanager.h
+++ b/libpurple/purplecredentialmanager.h
@@ -74,6 +74,8 @@
  * A function to be used as a callback with purple_credential_manager_foreach().
  *
  * Since: 3.0
+ *
+ * Deprecated: 3.0
  */
 PURPLE_AVAILABLE_TYPE_IN_3_0
 typedef void (*PurpleCredentialManagerForeachFunc)(PurpleCredentialProvider *provider, gpointer data);
@@ -106,6 +108,19 @@
 PurpleCredentialManager *purple_credential_manager_get_default(void);
 
 /**
+ * purple_credential_manager_get_default_as_model:
+ *
+ * Gets the default [class@CredentialManager] instance cast to a list model.
+ *
+ * Returns: (transfer none): The default #PurpleCredentialManager instance cast
+ *          to [iface@Gio.ListModel].
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+GListModel *purple_credential_manager_get_default_as_model(void);
+
+/**
  * purple_credential_manager_set_active:
  * @manager: The instance.
  * @id: (nullable): The id of the provider to use or %NULL to disable the
@@ -249,7 +264,7 @@
  *
  * Since: 3.0
  */
-PURPLE_AVAILABLE_IN_3_0
+PURPLE_DEPRECATED
 void purple_credential_manager_foreach(PurpleCredentialManager *manager, PurpleCredentialManagerForeachFunc func, gpointer data);
 
 /**
diff --git a/libpurple/tests/test_credential_manager.c b/libpurple/tests/test_credential_manager.c
--- a/libpurple/tests/test_credential_manager.c
+++ b/libpurple/tests/test_credential_manager.c
@@ -136,43 +136,126 @@
 }
 
 /******************************************************************************
- * Registration Tests
+ * Callbacks
  *****************************************************************************/
 static void
-test_purple_credential_manager_registration(void) {
+test_purple_credential_provider_items_changed_cb(GListModel *model,
+                                                 G_GNUC_UNUSED guint position,
+                                                 G_GNUC_UNUSED guint removed,
+                                                 G_GNUC_UNUSED guint added,
+                                                 gpointer data)
+{
+	guint *counter = data;
+
+	g_assert_true(PURPLE_IS_CREDENTIAL_MANAGER(model));
+	g_assert_true(G_IS_LIST_MODEL(model));
+
+	*counter = *counter + 1;
+}
+
+static void
+test_purple_credential_provider_added_removed_cb(PurpleCredentialManager *manager,
+                                                 PurpleCredentialProvider *provider,
+                                                 gpointer data)
+{
+	guint *counter = data;
+
+	g_assert_true(PURPLE_IS_CREDENTIAL_MANAGER(manager));
+	g_assert_true(PURPLE_IS_CREDENTIAL_PROVIDER(provider));
+
+	*counter = *counter + 1;
+}
+
+/******************************************************************************
+ * New Tests
+ *****************************************************************************/
+static void
+test_purple_credential_manager_new(void) {
+	PurpleCredentialManager *manager = NULL;
+
+	manager = g_object_new(PURPLE_TYPE_CREDENTIAL_MANAGER, NULL);
+
+	g_assert_true(PURPLE_IS_CREDENTIAL_MANAGER(manager));
+	g_assert_true(G_IS_LIST_MODEL(manager));
+
+	g_assert_finalize_object(manager);
+}
+
+/******************************************************************************
+ * Add/Remove Tests
+ *****************************************************************************/
+static void
+test_purple_credential_manager_add_remove(void) {
 	PurpleCredentialManager *manager = NULL;
 	PurpleCredentialProvider *provider = NULL;
 	GError *error = NULL;
 	gboolean result = FALSE;
+	guint added = 0;
+	guint removed = 0;
+	guint changed = 0;
 
 	manager = g_object_new(PURPLE_TYPE_CREDENTIAL_MANAGER, NULL);
 	g_assert_true(PURPLE_IS_CREDENTIAL_MANAGER(manager));
 
+	g_signal_connect(manager, "added",
+	                 G_CALLBACK(test_purple_credential_provider_added_removed_cb),
+	                 &added);
+	g_signal_connect(manager, "removed",
+	                 G_CALLBACK(test_purple_credential_provider_added_removed_cb),
+	                 &removed);
+	g_signal_connect(manager, "items-changed",
+	                 G_CALLBACK(test_purple_credential_provider_items_changed_cb),
+	                 &changed);
+
 	provider = test_purple_credential_provider_new();
 
 	/* Register the first time cleanly. */
+	added = 0;
+	removed = 0;
+	changed = 0;
 	result = purple_credential_manager_add(manager, provider, &error);
 	g_assert_no_error(error);
 	g_assert_true(result);
+	g_assert_cmpuint(added, ==, 1);
+	g_assert_cmpuint(removed, ==, 0);
+	g_assert_cmpuint(changed, ==, 1);
 
 	/* Register again and verify the error. */
+	added = 0;
+	removed = 0;
+	changed = 0;
 	result = purple_credential_manager_add(manager, provider, &error);
 	g_assert_error(error, PURPLE_CREDENTIAL_MANAGER_DOMAIN, 0);
 	g_clear_error(&error);
 
 	g_assert_false(result);
+	g_assert_cmpuint(added, ==, 0);
+	g_assert_cmpuint(removed, ==, 0);
+	g_assert_cmpuint(changed, ==, 0);
 
 	/* Unregister the provider. */
+	added = 0;
+	removed = 0;
+	changed = 0;
 	result = purple_credential_manager_remove(manager, provider, &error);
 	g_assert_no_error(error);
 	g_assert_true(result);
+	g_assert_cmpuint(added, ==, 0);
+	g_assert_cmpuint(removed, ==, 1);
+	g_assert_cmpuint(changed, ==, 1);
 
 	/* Unregister the provider again and verify the error. */
+	added = 0;
+	removed = 0;
+	changed = 0;
 	result = purple_credential_manager_remove(manager, provider, &error);
 	g_assert_error(error, PURPLE_CREDENTIAL_MANAGER_DOMAIN, 0);
 	g_clear_error(&error);
 
 	g_assert_false(result);
+	g_assert_cmpuint(added, ==, 0);
+	g_assert_cmpuint(removed, ==, 0);
+	g_assert_cmpuint(changed, ==, 0);
 
 	/* Final clean ups. */
 	g_clear_object(&provider);
@@ -556,8 +639,10 @@
 	g_test_init(&argc, &argv, NULL);
 	g_test_set_nonfatal_assertions();
 
-	g_test_add_func("/credential-manager/registration",
-	                test_purple_credential_manager_registration);
+	g_test_add_func("/credential-manager/new",
+	                test_purple_credential_manager_new);
+	g_test_add_func("/credential-manager/add-remove",
+	                test_purple_credential_manager_add_remove);
 	g_test_add_func("/credential-manager/set-active/null",
 	                test_purple_credential_manager_set_active_null);
 	g_test_add_func("/credential-manager/set-active/non-existent",
