# HG changeset patch
# Node ID 0627e2d5078cf08c309ed6fca67210c8b7b4df1c
# Parent  4ff60371673fb56fb0de96de16052ca9f37a3243
diff --git a/libpurple/purplecontact.c b/libpurple/purplecontact.c
--- a/libpurple/purplecontact.c
+++ b/libpurple/purplecontact.c
@@ -261,3 +261,31 @@
 
 	return g_task_propagate_pointer(task, error);
 }
+
+int
+purple_contact_compare(PurpleContact *a, PurpleContact *b) {
+	int result;
+
+	if(a != NULL && b == NULL) {
+		return -1;
+	}
+	if(a == NULL && b != NULL) {
+		return 1;
+	}
+	if(a == NULL && b == NULL) {
+		return 0;
+	}
+
+	result = purple_account_compare(a->account, b->account);
+	if(result != 0) {
+		return result;
+	}
+
+	return purple_contact_info_compare(PURPLE_CONTACT_INFO(a),
+	                                   PURPLE_CONTACT_INFO(b));
+}
+
+gboolean
+purple_contact_equal(PurpleContact *a, PurpleContact *b) {
+	return purple_contact_compare(a, b) == 0;
+}
diff --git a/libpurple/purplecontact.h b/libpurple/purplecontact.h
--- a/libpurple/purplecontact.h
+++ b/libpurple/purplecontact.h
@@ -142,6 +142,35 @@
 PURPLE_AVAILABLE_IN_3_0
 PurpleConversation *purple_contact_create_dm_finish(PurpleContact *contact, GAsyncResult *result, GError **error);
 
+/**
+ * purple_contact_compare:
+ * @a: (nullable): the first instance
+ * @b: (nullable): the second instance
+ *
+ * Compares contacts @a and @b.
+ *
+ * Returns: less than 0 if @a should be sorted before @b, 0 if they sorted
+ *          equally, and greater than 0 if @a should be sorted after @b.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+int purple_contact_compare(PurpleContact *a, PurpleContact *b);
+
+/**
+ * purple_contact_equal:
+ * @a: (nullable): the first instance
+ * @b: (nullable): the second instance
+ *
+ * Checks two [class@Contact] objects for equality.
+ *
+ * Returns: true if @a and @b are equal; false otherwise.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_contact_equal(PurpleContact *a, PurpleContact *b);
+
 G_END_DECLS
 
 #endif /* PURPLE_CONTACT_H */
diff --git a/libpurple/tests/test_contact.c b/libpurple/tests/test_contact.c
--- a/libpurple/tests/test_contact.c
+++ b/libpurple/tests/test_contact.c
@@ -77,6 +77,212 @@
 }
 
 /******************************************************************************
+ * Compare
+ *****************************************************************************/
+static void
+test_purple_contact_compare_not_null__null(void) {
+	PurpleAccount *account = NULL;
+	PurpleContact *contact = NULL;
+
+	account = purple_account_new("test", "test");
+	contact = g_object_new(
+		PURPLE_TYPE_CONTACT,
+		"account", account,
+		"id", "id1",
+		NULL);
+
+	g_assert_cmpint(purple_contact_compare(contact, NULL), <, 0);
+
+	g_assert_finalize_object(contact);
+	g_assert_finalize_object(account);
+}
+
+static void
+test_purple_contact_compare_null__not_null(void) {
+	PurpleAccount *account = NULL;
+	PurpleContact *contact = NULL;
+
+	account = purple_account_new("test", "test");
+	contact = g_object_new(
+		PURPLE_TYPE_CONTACT,
+		"account", account,
+		"id", "id1",
+		NULL);
+
+	g_assert_cmpint(purple_contact_compare(NULL, contact), >, 0);
+
+	g_assert_finalize_object(contact);
+	g_assert_finalize_object(account);
+}
+
+static void
+test_purple_contact_compare_null__null(void) {
+	g_assert_cmpint(purple_contact_compare(NULL, NULL), ==, 0);
+}
+
+static void
+test_purple_contact_compare_not_null__not_null(void) {
+	PurpleAccount *account_a = NULL;
+	PurpleAccount *account_b = NULL;
+	PurpleContact *contact_a = NULL;
+	PurpleContact *contact_b = NULL;
+
+	account_a = g_object_new(
+		PURPLE_TYPE_ACCOUNT,
+		"username", "test_a",
+		"protocol-id", "test",
+		"enabled", FALSE,
+		NULL);
+	account_b = g_object_new(
+		PURPLE_TYPE_ACCOUNT,
+		"username", "test_b",
+		"protocol-id", "test",
+		"enabled", FALSE,
+		NULL);
+	contact_a = g_object_new(
+		PURPLE_TYPE_CONTACT,
+		"account", account_a,
+		NULL);
+	contact_b = g_object_new(
+		PURPLE_TYPE_CONTACT,
+		"account", account_b,
+		NULL);
+
+	/* Account a and b have no id and should compare equal. */
+	g_assert_cmpint(purple_contact_compare(contact_a, contact_b), ==, 0);
+
+	/* Change display_name so the contact_info objects will be different. */
+	purple_contact_info_set_display_name(PURPLE_CONTACT_INFO(contact_a), "name_a");
+	purple_contact_info_set_display_name(PURPLE_CONTACT_INFO(contact_b), "name_b");
+	g_assert_cmpint(purple_contact_compare(contact_a, contact_b), <, 0);
+
+	/* Set the account ids so the account member will be different too. */
+	g_object_set(account_a, "id", "account_id_a", NULL);
+	g_object_set(account_b, "id", "account_id_b", NULL);
+	g_assert_cmpint(purple_contact_compare(contact_b, contact_a), >, 0);
+
+	/* Set the display name of contact_b to that of contact_a. The contact info
+	 * is equal now, but account is still different.
+	 */
+	purple_contact_info_set_display_name(PURPLE_CONTACT_INFO(contact_b), "name_a");
+	g_assert_cmpint(purple_contact_compare(contact_b, contact_a), >, 0);
+
+	/* Set the account id of account_b to that of account_a. Both contact info and
+	 * account are equal now.
+	 */
+	g_object_set(account_b, "id", "account_id_a", NULL);
+	g_assert_cmpint(purple_contact_compare(contact_a, contact_b), ==, 0);
+
+	g_assert_finalize_object(contact_a);
+	g_assert_finalize_object(contact_b);
+	g_assert_finalize_object(account_a);
+	g_assert_finalize_object(account_b);
+}
+
+/******************************************************************************
+ * Equal
+ *****************************************************************************/
+static void
+test_purple_contact_equal_not_null__null(void) {
+	PurpleAccount *account = NULL;
+	PurpleContact *contact = NULL;
+
+	account = purple_account_new("test", "test");
+	contact = g_object_new(
+		PURPLE_TYPE_CONTACT,
+		"account", account,
+		"id", "id1",
+		NULL);
+
+	g_assert_false(purple_contact_equal(contact, NULL));
+
+	g_assert_finalize_object(contact);
+	g_assert_finalize_object(account);
+}
+
+static void
+test_purple_contact_equal_null__not_null(void) {
+	PurpleAccount *account = NULL;
+	PurpleContact *contact = NULL;
+
+	account = purple_account_new("test", "test");
+	contact = g_object_new(
+		PURPLE_TYPE_CONTACT,
+		"account", account,
+		"id", "id1",
+		NULL);
+
+	g_assert_false(purple_contact_equal(NULL, contact));
+
+	g_assert_finalize_object(contact);
+	g_assert_finalize_object(account);
+}
+
+static void
+test_purple_contact_equal_null__null(void) {
+	g_assert_true(purple_contact_equal(NULL, NULL));
+}
+
+static void
+test_purple_contact_equal_not_null__not_null(void) {
+	PurpleAccount *account_a = NULL;
+	PurpleAccount *account_b = NULL;
+	PurpleContact *contact_a = NULL;
+	PurpleContact *contact_b = NULL;
+
+	account_a = g_object_new(
+		PURPLE_TYPE_ACCOUNT,
+		"username", "test_a",
+		"protocol-id", "test",
+		"enabled", FALSE,
+		NULL);
+	account_b = g_object_new(
+		PURPLE_TYPE_ACCOUNT,
+		"username", "test_b",
+		"protocol-id", "test",
+		"enabled", FALSE,
+		NULL);
+	contact_a = g_object_new(
+		PURPLE_TYPE_CONTACT,
+		"account", account_a,
+		NULL);
+	contact_b = g_object_new(
+		PURPLE_TYPE_CONTACT,
+		"account", account_b,
+		NULL);
+
+	/* Account a and b have no id and should compare equal. */
+	g_assert_true(purple_contact_equal(contact_a, contact_b));
+
+	/* Change display_name so the contact_info objects will be different. */
+	purple_contact_info_set_display_name(PURPLE_CONTACT_INFO(contact_a), "name_a");
+	purple_contact_info_set_display_name(PURPLE_CONTACT_INFO(contact_b), "name_b");
+	g_assert_false(purple_contact_equal(contact_a, contact_b));
+
+	/* Set the account ids so the account members will be different too. */
+	g_object_set(account_a, "id", "account_id_a", NULL);
+	g_object_set(account_b, "id", "account_id_b", NULL);
+	g_assert_false(purple_contact_equal(contact_a, contact_b));
+
+	/* Set the display name of contact_b to that of contact_a. The contact info
+	 * is equal now, but account is still different.
+	 */
+	purple_contact_info_set_display_name(PURPLE_CONTACT_INFO(contact_b), "name_a");
+	g_assert_false(purple_contact_equal(contact_a, contact_b));
+
+	/* Set the account id of account_b to that of account_a. Both contact info and
+	 * account are equal now.
+	 */
+	g_object_set(account_b, "id", "account_id_a", NULL);
+	g_assert_true(purple_contact_equal(contact_a, contact_b));
+
+	g_assert_finalize_object(contact_a);
+	g_assert_finalize_object(contact_b);
+	g_assert_finalize_object(account_a);
+	g_assert_finalize_object(account_b);
+}
+
+/******************************************************************************
  * Main
  *****************************************************************************/
 int
@@ -89,5 +295,24 @@
 	g_test_add_func("/contact/properties",
 	                test_purple_contact_properties);
 
+	g_test_add_func("/contact/compare/not_null__null",
+	                test_purple_contact_compare_not_null__null);
+	g_test_add_func("/contact/compare/null__not_null",
+	                test_purple_contact_compare_null__not_null);
+	g_test_add_func("/contact/compare/null__null",
+	                test_purple_contact_compare_null__null);
+	g_test_add_func("/contact/compare/not_null__not_null",
+	                test_purple_contact_compare_not_null__not_null);
+
+	g_test_add_func("/contact/equal/not_null__null",
+	                test_purple_contact_equal_not_null__null);
+	g_test_add_func("/contact/equal/null__not_null",
+	                test_purple_contact_equal_null__not_null);
+	g_test_add_func("/contact/equal/null__null",
+	                test_purple_contact_equal_null__null);
+	g_test_add_func("/contact/equal/not_null__not_null",
+	                test_purple_contact_equal_not_null__not_null);
+
+
 	return g_test_run();
 }
