# HG changeset patch
# Node ID fc81d36cc9b1f423cb3a0cbf048252129406de1b
# Parent  7709065d96eb640c6f149f393fd8fcc411e80a6b
diff --git a/libpurple/purpleperson.c b/libpurple/purpleperson.c
--- a/libpurple/purpleperson.c
+++ b/libpurple/purpleperson.c
@@ -61,6 +61,10 @@
 purple_person_priority_contact_info_notify_cb(GObject *obj,
                                               G_GNUC_UNUSED GParamSpec *pspec,
                                               gpointer data);
+static void
+purple_person_presence_notify_cb(GObject *obj,
+                                 GParamSpec *pspec,
+                                 gpointer data);
 
 /******************************************************************************
  * Helpers
@@ -106,8 +110,12 @@
 	g_list_model_items_changed(G_LIST_MODEL(person), 0, n_items, n_items);
 	g_object_notify_by_pspec(G_OBJECT(person), properties[PROP_N_ITEMS]);
 
+	/* If we have at least 1 contact, it is the new priority. */
+	if(person->contacts->len > 0) {
+		new_priority = g_ptr_array_index(person->contacts, 0);
+	}
+
 	/* See if the priority contact changed. */
-	new_priority = g_ptr_array_index(person->contacts, 0);
 	if(original_priority != new_priority) {
 		PurpleAvatar *old_avatar = NULL;
 		PurpleAvatar *new_avatar = NULL;
@@ -171,6 +179,31 @@
 	return purple_contact_info_matches(info, needle);
 }
 
+static gboolean
+purple_person_remove_contact_info_internal(PurplePerson *person,
+                                           PurpleContactInfo *info)
+{
+	PurplePresence *presence = NULL;
+	guint index = 0;
+	gboolean found = FALSE;
+
+	found = g_ptr_array_find(person->contacts, info, &index);
+	if(!found) {
+		return FALSE;
+	}
+
+	presence = purple_contact_info_get_presence(info);
+	g_signal_handlers_disconnect_by_func(presence,
+	                                     purple_person_presence_notify_cb,
+	                                     person);
+
+	purple_contact_info_set_person(info, NULL);
+
+	g_ptr_array_remove_index_fast(person->contacts, index);
+
+	return TRUE;
+}
+
 /******************************************************************************
  * Callbacks
  *****************************************************************************/
@@ -699,42 +732,46 @@
 	purple_person_sort_contacts(person, current_priority);
 }
 
+void
+purple_person_remove_all_contact_infos(PurplePerson *person) {
+	PurpleContactInfo *current_priority = NULL;
+
+	g_return_if_fail(PURPLE_IS_PERSON(person));
+
+	current_priority = purple_person_get_priority_contact_info(person);
+
+	/* This loop is weird because we're removing from the array we are
+	 * iterating. We could go backwards, but this is fine as we just work on
+	 * the first item. The length is decremented in the remove function so we
+	 * don't need to do it ourselves.
+	 */
+	while(person->contacts->len > 0) {
+		PurpleContactInfo *info = g_ptr_array_index(person->contacts, 0);
+
+		purple_person_remove_contact_info_internal(person, info);
+	}
+
+	purple_person_sort_contacts(person, current_priority);
+}
+
 gboolean
 purple_person_remove_contact_info(PurplePerson *person,
                                   PurpleContactInfo *info)
 {
 	PurpleContactInfo *current_priority = NULL;
-	gboolean removed = FALSE;
+	gboolean found = FALSE;
 
 	g_return_val_if_fail(PURPLE_IS_PERSON(person), FALSE);
 	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), FALSE);
 
-	/* Ref the contact info to avoid a use-after free. */
-	g_object_ref(info);
-
 	current_priority = purple_person_get_priority_contact_info(person);
 
-	/* g_ptr_array_remove calls g_object_unref because we passed it in as a
-	 * GDestroyNotify.
-	 */
-	removed = g_ptr_array_remove(person->contacts, info);
-
-	if(removed) {
-		PurplePresence *presence = purple_contact_info_get_presence(info);
-
-		g_signal_handlers_disconnect_by_func(presence,
-		                                     purple_person_presence_notify_cb,
-		                                     person);
-
-		purple_contact_info_set_person(info, NULL);
-
+	found = purple_person_remove_contact_info_internal(person, info);
+	if(found) {
 		purple_person_sort_contacts(person, current_priority);
 	}
 
-	/* Remove our reference. */
-	g_object_unref(info);
-
-	return removed;
+	return TRUE;
 }
 
 PurpleContactInfo *
diff --git a/libpurple/purpleperson.h b/libpurple/purpleperson.h
--- a/libpurple/purpleperson.h
+++ b/libpurple/purpleperson.h
@@ -240,6 +240,16 @@
 void purple_person_add_contact_info(PurplePerson *person, PurpleContactInfo *info);
 
 /**
+ * purple_person_remove_all_contact_infos:
+ *
+ * Removes all of the contact infos from a person.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+void purple_person_remove_all_contact_infos(PurplePerson *person);
+
+/**
  * purple_person_remove_contact_info:
  * @person: The instance.
  * @info: The [class@Purple.ContactInfo] to remove.
diff --git a/libpurple/tests/test_person.c b/libpurple/tests/test_person.c
--- a/libpurple/tests/test_person.c
+++ b/libpurple/tests/test_person.c
@@ -30,9 +30,9 @@
                                     G_GNUC_UNUSED guint added,
                                     gpointer data)
 {
-	gboolean *called = data;
+	guint *counter = data;
 
-	*called = TRUE;
+	*counter = *counter + 1;
 }
 
 static void
@@ -56,7 +56,7 @@
 
 	g_assert_true(PURPLE_IS_PERSON(person));
 
-	g_clear_object(&person);
+	g_assert_finalize_object(person);
 }
 
 static void
@@ -124,8 +124,8 @@
 	g_clear_object(&tags);
 
 	/* Additional cleanup. */
-	g_clear_object(&avatar);
-	g_clear_object(&person);
+	g_assert_finalize_object(person);
+	g_assert_finalize_object(avatar);
 }
 
 static void
@@ -150,9 +150,14 @@
 	/* Make sure the person's avatar is overriding the contact info. */
 	g_assert_true(purple_person_get_avatar_for_display(person) == avatar);
 
-	g_clear_object(&info);
-	g_clear_object(&person);
-	g_clear_object(&avatar);
+	/* Contact info's have a reference on the person, so the easiest way to
+	 * remove that is to remove them from the person.
+	 */
+	purple_person_remove_contact_info(person, info);
+
+	g_assert_finalize_object(person);
+	g_assert_finalize_object(info);
+	g_assert_finalize_object(avatar);
 }
 
 static void
@@ -186,9 +191,14 @@
 	purple_contact_info_set_avatar(info, avatar);
 	g_assert_cmpuint(called, ==, 1);
 
-	g_clear_object(&info);
-	g_clear_object(&person);
-	g_clear_object(&avatar);
+	/* Contact info's have a reference on the person, so the easiest way to
+	 * remove that is to remove them from the person.
+	 */
+	purple_person_remove_contact_info(person, info);
+
+	g_assert_finalize_object(person);
+	g_assert_finalize_object(info);
+	g_assert_finalize_object(avatar);
 }
 
 static void
@@ -223,9 +233,14 @@
 	g_assert_cmpuint(color_called, ==, 0);
 	g_assert_cmpuint(color_for_display_called, ==, 0);
 
+	/* Contact info's have a reference on the person, so the easiest way to
+	 * remove that is to remove them from the person.
+	 */
+	purple_person_remove_contact_info(person, info);
+
 	/* Cleanup. */
-	g_clear_object(&info);
-	g_clear_object(&person);
+	g_assert_finalize_object(person);
+	g_assert_finalize_object(info);
 }
 
 static void
@@ -256,8 +271,13 @@
 	purple_contact_info_set_color(info, "#6789ab");
 	g_assert_cmpuint(called, ==, 1);
 
-	g_clear_object(&info);
-	g_clear_object(&person);
+	/* Contact info's have a reference on the person, so the easiest way to
+	 * remove that is to remove them from the person.
+	 */
+	purple_person_remove_contact_info(person, info);
+
+	g_assert_finalize_object(person);
+	g_assert_finalize_object(info);
 }
 
 static void
@@ -275,8 +295,13 @@
 	g_assert_cmpstr(purple_person_get_name_for_display(person), ==,
 	                "person-alias");
 
-	g_clear_object(&info);
-	g_clear_object(&person);
+	/* Contact info's have a reference on the person, so the easiest way to
+	 * remove that is to remove them from the person.
+	 */
+	purple_person_remove_contact_info(person, info);
+
+	g_assert_finalize_object(person);
+	g_assert_finalize_object(info);
 }
 
 static void
@@ -303,8 +328,13 @@
 	g_assert_cmpstr(purple_person_get_name_for_display(person), ==, "clu");
 	g_assert_cmpuint(called, ==, 2);
 
-	g_clear_object(&info);
-	g_clear_object(&person);
+	/* Contact info's have a reference on the person, so the easiest way to
+	 * remove that is to remove them from the person.
+	 */
+	purple_person_remove_contact_info(person, info);
+
+	g_assert_finalize_object(person);
+	g_assert_finalize_object(info);
 }
 
 static void
@@ -314,7 +344,7 @@
 	PurplePerson *person1 = NULL;
 	guint n_items = 0;
 	gboolean removed = FALSE;
-	gboolean changed = FALSE;
+	guint changed = 0;
 
 	info = purple_contact_info_new("id");
 	person = purple_person_new();
@@ -326,24 +356,23 @@
 	purple_person_add_contact_info(person, info);
 	n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
 	g_assert_cmpuint(n_items, ==, 1);
-	g_assert_true(changed);
+	g_assert_cmpuint(changed, ==, 1);
 
 	person1 = purple_contact_info_get_person(info);
 	g_assert_true(person1 == person);
 
-	changed = FALSE;
-
+	changed = 0;
 	removed = purple_person_remove_contact_info(person, info);
 	g_assert_true(removed);
+	g_assert_cmpuint(changed, ==, 1);
 	n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
 	g_assert_cmpuint(n_items, ==, 0);
-	g_assert_true(changed);
 
 	person1 = purple_contact_info_get_person(info);
 	g_assert_null(person1);
 
-	g_clear_object(&person);
-	g_clear_object(&info);
+	g_assert_finalize_object(person);
+	g_assert_finalize_object(info);
 }
 
 static void
@@ -352,7 +381,7 @@
 	GPtrArray *infos = NULL;
 	guint n_items = 0;
 	const int n_infos = 5;
-	gboolean changed = FALSE;
+	guint changed = 0;
 
 	person = purple_person_new();
 	g_signal_connect(person, "items-changed",
@@ -363,7 +392,7 @@
 		PurpleContactInfo *info = NULL;
 		char *username = NULL;
 
-		changed = FALSE;
+		changed = 0;
 
 		n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
 		g_assert_cmpuint(n_items, ==, i);
@@ -382,14 +411,14 @@
 		purple_person_add_contact_info(person, info);
 		n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
 		g_assert_cmpuint(n_items, ==, i + 1);
-		g_assert_true(changed);
+		g_assert_cmpuint(changed, ==, 1);
 	}
 
 	for(int i = 0; i < n_infos; i++) {
 		PurpleContactInfo *info = g_ptr_array_index(infos, i);
 		gboolean removed = FALSE;
 
-		changed = FALSE;
+		changed = 0;
 
 		n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
 		g_assert_cmpuint(n_items, ==, n_infos - i);
@@ -400,7 +429,7 @@
 		n_items = g_list_model_get_n_items(G_LIST_MODEL(person));
 		g_assert_cmpuint(n_items, ==, n_infos - (i + 1));
 
-		g_assert_true(changed);
+		g_assert_cmpuint(changed, ==, 1);
 	}
 
 	/* Final sanity check that the person has no more contacts. */
@@ -409,7 +438,7 @@
 
 	g_ptr_array_free(infos, TRUE);
 
-	g_clear_object(&person);
+	g_assert_finalize_object(person);
 }
 
 static void
@@ -440,9 +469,13 @@
 	priority = purple_person_get_priority_contact_info(person);
 	g_assert_true(priority == info);
 
-	g_clear_object(&person);
-	g_clear_object(&info);
-	g_clear_object(&presence);
+	/* Contact info's have a reference on the person, so the easiest way to
+	 * remove that is to remove them from the person.
+	 */
+	purple_person_remove_contact_info(person, info);
+
+	g_assert_finalize_object(person);
+	g_assert_finalize_object(info);
 }
 
 static void
@@ -531,10 +564,12 @@
 	g_assert_cmpuint(called, ==, 1);
 
 	/* Cleanup. */
+	purple_person_remove_all_contact_infos(person);
+
 	g_clear_object(&sorted_contact);
 	g_clear_object(&sorted_presence);
 
-	g_clear_object(&person);
+	g_assert_finalize_object(person);
 }
 
 /******************************************************************************
@@ -546,7 +581,7 @@
 
 	g_assert_true(purple_person_matches(person, NULL));
 
-	g_clear_object(&person);
+	g_assert_finalize_object(person);
 }
 
 static void
@@ -555,7 +590,7 @@
 
 	g_assert_true(purple_person_matches(person, ""));
 
-	g_clear_object(&person);
+	g_assert_finalize_object(person);
 }
 
 static void
@@ -567,7 +602,7 @@
 	g_assert_true(purple_person_matches(person, "the"));
 	g_assert_false(purple_person_matches(person, "what"));
 
-	g_clear_object(&person);
+	g_assert_finalize_object(person);
 }
 
 static void
@@ -577,11 +612,15 @@
 
 	purple_contact_info_set_username(info, "user1");
 	purple_person_add_contact_info(person, info);
-	g_clear_object(&info);
 
 	g_assert_true(purple_person_matches(person, "user1"));
 
-	g_clear_object(&person);
+	/* Contact info's have a reference on the person, so the easiest way to
+	 * remove that is to remove them from the person.
+	 */
+	purple_person_remove_contact_info(person, info);
+
+	g_assert_finalize_object(person);
 }
 
 /******************************************************************************
