--- a/libpurple/purplecontactmanager.c Tue Dec 13 22:53:03 2022 -0600
+++ b/libpurple/purplecontactmanager.c Wed Dec 14 01:27:42 2022 -0600
@@ -27,6 +27,8 @@
static guint signals[N_SIGNALS] = {0, };
@@ -35,10 +37,17 @@
static PurpleContactManager *default_manager = NULL;
+/* Necessary prototype. */ +static void purple_contact_manager_contact_person_changed_cb(GObject *obj, /******************************************************************************
*****************************************************************************/
@@ -127,9 +136,72 @@
/******************************************************************************
+ *****************************************************************************/ +purple_contact_manager_contact_person_changed_cb(GObject *obj, + G_GNUC_UNUSED GParamSpec *pspec, + PurpleContact *contact = PURPLE_CONTACT(obj); + PurpleContactManager *manager = data; + PurplePerson *person = NULL; + person = purple_contact_info_get_person(PURPLE_CONTACT_INFO(contact)); + /* If the person is now NULL, we leaving the existing person in place as + * we don't want to potentially delete user data. + if(!PURPLE_IS_PERSON(person)) { + /* At this point the person changed or is new so we need to add the new + purple_contact_manager_add_person(manager, person); +/****************************************************************************** + * GListModel Implementation + *****************************************************************************/ +purple_contact_manager_get_item_type(G_GNUC_UNUSED GListModel *list) { + return PURPLE_TYPE_PERSON; +purple_contact_manager_get_n_items(GListModel *list) { + PurpleContactManager *manager = PURPLE_CONTACT_MANAGER(list); + return manager->people->len; +purple_contact_manager_get_item(GListModel *list, guint position) { + PurpleContactManager *manager = PURPLE_CONTACT_MANAGER(list); + PurpleContact *contact = NULL; + if(position < manager->people->len) { + contact = g_object_ref(g_ptr_array_index(manager->people, position)); +pidgin_contact_manager_list_model_iface_init(GListModelInterface *iface) { + iface->get_item_type = purple_contact_manager_get_item_type; + iface->get_n_items = purple_contact_manager_get_n_items; + iface->get_item = purple_contact_manager_get_item; +/****************************************************************************** *****************************************************************************/
-G_DEFINE_TYPE(PurpleContactManager, purple_contact_manager, G_TYPE_OBJECT)
+G_DEFINE_FINAL_TYPE_WITH_CODE(PurpleContactManager, purple_contact_manager, + G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL, + pidgin_contact_manager_list_model_iface_init)) purple_contact_manager_dispose(GObject *obj) {
@@ -139,6 +211,11 @@
g_hash_table_remove_all(manager->accounts);
+ if(manager->people != NULL) { + g_ptr_array_free(manager->people, TRUE); + manager->people = NULL; G_OBJECT_CLASS(purple_contact_manager_parent_class)->dispose(obj);
@@ -157,6 +234,12 @@
purple_contact_manager_init(PurpleContactManager *manager) {
manager->accounts = g_hash_table_new_full(g_direct_hash, g_direct_equal,
g_object_unref, g_object_unref);
+ /* 100 Seems like a reasonable default of the number people on your contact + manager->people = g_ptr_array_new_full(100, + (GDestroyNotify)g_object_unref); @@ -207,6 +290,51 @@
+ * PurpleContactManager::person-added: + * @manager: The instance. + * @person: The [class@Purple.Person] that was added. + * Emitted after @person has been added to @manager. This is typically done + * when a contact is added via [method@Purple.ContactManager.add] but can + * also happen if [method@Purple.ContactManager.add_person] is called. + signals[SIG_PERSON_ADDED] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), + * PurpleContactManager::person-removed: + * @manager: The instance. + * @person: The [class@Purple.Person] that was removed. + * Emitted after @person has been removed from @manager. This typically + * happens when [method@Purple.ContactManager.remove_person] is called. + signals[SIG_PERSON_REMOVED] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), /******************************************************************************
@@ -274,6 +402,21 @@
+ PurpleContactInfo *info = PURPLE_CONTACT_INFO(contact); + PurplePerson *person = purple_contact_info_get_person(info); + /* If the contact already has a person, add the person to our list of + if(PURPLE_IS_PERSON(person)) { + purple_contact_manager_add_person(manager, person); + /* Add a notify on the person property to track changes. */ + g_signal_connect_object(contact, "notify::person", + G_CALLBACK(purple_contact_manager_contact_person_changed_cb), g_signal_emit(manager, signals[SIG_ADDED], 0, contact);
@@ -485,3 +628,67 @@
/* purple_contact_manager_add adds its own reference, so free our copy. */
g_clear_object(&contact);
+purple_contact_manager_add_person(PurpleContactManager *manager, + g_return_if_fail(PURPLE_IS_CONTACT_MANAGER(manager)); + g_return_if_fail(PURPLE_IS_PERSON(person)); + /* If the person is already known, bail. */ + if(g_ptr_array_find(manager->people, person, &index)) { + /* Add the person and emit our signals. */ + g_ptr_array_add(manager->people, g_object_ref(person)); + g_list_model_items_changed(G_LIST_MODEL(manager), index, 0, 1); + g_signal_emit(manager, signals[SIG_PERSON_ADDED], 0, person); +purple_contact_manager_remove_person(PurpleContactManager *manager, + gboolean remove_contacts) + g_return_if_fail(PURPLE_IS_CONTACT_MANAGER(manager)); + g_return_if_fail(PURPLE_IS_PERSON(person)); + if(!g_ptr_array_find(manager->people, person, &index)) { + guint n = g_list_model_get_n_items(G_LIST_MODEL(person)); + for(guint i = 0; i < n; i++) { + PurpleContact *contact = NULL; + contact = g_list_model_get_item(G_LIST_MODEL(person), i); + if(PURPLE_IS_CONTACT(contact)) { + purple_contact_manager_remove(manager, contact); + g_object_unref(contact); + /* Add a ref to the person, so we can emit the removed signal after it + * was actually removed, as our GPtrArray may be holding the last + g_ptr_array_remove_index(manager->people, index); + g_list_model_items_changed(G_LIST_MODEL(manager), index, 1, 0); + /* Emit the removed signal and clear our temporary reference. */ + g_signal_emit(manager, signals[SIG_PERSON_REMOVED], 0, person); + g_object_unref(person); --- a/libpurple/purplecontactmanager.h Tue Dec 13 22:53:03 2022 -0600
+++ b/libpurple/purplecontactmanager.h Wed Dec 14 01:27:42 2022 -0600
@@ -158,6 +158,36 @@
void purple_contact_manager_add_buddy(PurpleContactManager *manager, PurpleBuddy *buddy);
+ * purple_contact_manager_add_person: + * @manager: The instance. + * @person: The [class@Purple.Person to add]. + * Adds all of the contacts contained in @person to @manager. + * This function is mostly intended for unit testing and importing. You + * typically you won't need to call this directly as @manager will + * automatically add the [class@Purple.Person] instance when + * [method@Purple.ContactManager.add] is called. +void purple_contact_manager_add_person(PurpleContactManager *manager, PurplePerson *person); + * purple_contact_manager_remove_person: + * @manager: The instance. + * @person: The [class@Purple.Person] to remove. + * @remove_contacts: Whether or not the contacts should be removed from + * Removes @person from @manager optionally removing all of the contacts + * contained in @person as well if @remove_contacts is %TRUE. +void purple_contact_manager_remove_person(PurpleContactManager *manager, PurplePerson *person, gboolean remove_contacts); #endif /* PURPLE_CONTACT_MANAGER_H */
--- a/libpurple/purpleperson.c Tue Dec 13 22:53:03 2022 -0600
+++ b/libpurple/purpleperson.c Wed Dec 14 01:27:42 2022 -0600
@@ -452,3 +452,10 @@
return g_ptr_array_index(person->contacts, 0);
+purple_person_has_contacts(PurplePerson *person) { + g_return_val_if_fail(PURPLE_IS_PERSON(person), FALSE); + return person->contacts->len > 0; --- a/libpurple/purpleperson.h Tue Dec 13 22:53:03 2022 -0600
+++ b/libpurple/purpleperson.h Wed Dec 14 01:27:42 2022 -0600
@@ -180,6 +180,18 @@
PurpleContactInfo *purple_person_get_priority_contact_info(PurplePerson *person);
+ * purple_person_has_contacts: + * @person: The instance. + * Gets whether or not @person has any contacts. + * Returns: %TRUE if @person has at least one contact, otherwise %FALSE. +gboolean purple_person_has_contacts(PurplePerson *person); #endif /* PURPLE_PERSON_H */
--- a/libpurple/tests/test_contact_manager.c Tue Dec 13 22:53:03 2022 -0600
+++ b/libpurple/tests/test_contact_manager.c Wed Dec 14 01:27:42 2022 -0600
@@ -347,6 +347,123 @@
g_clear_object(&contact);
+/****************************************************************************** + *****************************************************************************/ +test_purple_contact_manager_person_add_remove(void) { + PurpleContactManager *manager = NULL; + PurplePerson *person = NULL; + GListModel *model = NULL; + int removed_called = 0; + manager = g_object_new(PURPLE_TYPE_CONTACT_MANAGER, NULL); + model = G_LIST_MODEL(manager); + g_assert_true(PURPLE_IS_CONTACT_MANAGER(manager)); + /* Wire up our signals. */ + g_signal_connect(manager, "person-added", + G_CALLBACK(test_purple_contact_manager_increment_cb), + g_signal_connect(manager, "person-removed", + G_CALLBACK(test_purple_contact_manager_increment_cb), + /* Create the person and add it to the manager. */ + person = purple_person_new(); + purple_contact_manager_add_person(manager, person); + /* Make sure the person is available. */ + g_assert_cmpuint(g_list_model_get_n_items(model), ==, 1); + /* Make sure the added signal was called. */ + g_assert_cmpint(added_called, ==, 1); + /* Remove the contact. */ + purple_contact_manager_remove_person(manager, person, FALSE); + g_assert_cmpint(removed_called, ==, 1); + /* Make sure the person was removed. */ + g_assert_cmpuint(g_list_model_get_n_items(model), ==, 0); + g_clear_object(&person); + g_clear_object(&manager); +test_purple_contact_manager_person_add_via_contact_remove_person_with_contacts(void) + PurpleAccount *account = NULL; + PurpleContact *contact = NULL; + PurpleContactManager *manager = NULL; + PurplePerson *person = NULL; + GListModel *contacts = NULL; + GListModel *model = NULL; + int contact_added_called = 0; + int contact_removed_called = 0; + int person_added_called = 0; + int person_removed_called = 0; + manager = g_object_new(PURPLE_TYPE_CONTACT_MANAGER, NULL); + model = G_LIST_MODEL(manager); + g_assert_true(PURPLE_IS_CONTACT_MANAGER(manager)); + /* Wire up our signals. */ + g_signal_connect(manager, "added", + G_CALLBACK(test_purple_contact_manager_increment_cb), + &contact_added_called); + g_signal_connect(manager, "removed", + G_CALLBACK(test_purple_contact_manager_increment_cb), + &contact_removed_called); + g_signal_connect(manager, "person-added", + G_CALLBACK(test_purple_contact_manager_increment_cb), + g_signal_connect(manager, "person-removed", + G_CALLBACK(test_purple_contact_manager_increment_cb), + &person_removed_called); + /* Create all of our objects. */ + account = purple_account_new("test", "test"); + contact = purple_contact_new(account, "foo"); + person = purple_person_new(); + purple_person_add_contact_info(person, contact); + /* Add the contact to the manager. */ + purple_contact_manager_add(manager, contact); + /* Make sure the contact is available. */ + contacts = purple_contact_manager_get_all(manager, account); + g_assert_nonnull(contacts); + /* Make sure the contact and the person were added. */ + g_assert_cmpuint(g_list_model_get_n_items(contacts), ==, 1); + g_assert_cmpuint(g_list_model_get_n_items(model), ==, 1); + /* Make sure the added signals were called. */ + g_assert_cmpint(contact_added_called, ==, 1); + g_assert_cmpint(person_added_called, ==, 1); + /* Remove the person and the contacts. */ + purple_contact_manager_remove_person(manager, person, TRUE); + g_assert_cmpint(contact_removed_called, ==, 1); + g_assert_cmpint(person_removed_called, ==, 1); + /* Make sure the person and contact were removed. */ + g_assert_cmpuint(g_list_model_get_n_items(model), ==, 0); + g_assert_cmpuint(g_list_model_get_n_items(contacts), ==, 0); + g_clear_object(&account); + g_clear_object(&contact); + g_clear_object(&person); + g_clear_object(&manager); /******************************************************************************
*****************************************************************************/
@@ -376,5 +493,10 @@
g_test_add_func("/contact-manager/add-buddy",
test_purple_contact_manager_add_buddy);
+ g_test_add_func("/contact-manager/person/add-remove", + test_purple_contact_manager_person_add_remove); + g_test_add_func("/contact-manager/person/add-via-contact-remove-person-with-contacts", + test_purple_contact_manager_person_add_via_contact_remove_person_with_contacts);