Wed, 14 Dec 2022 01:27:42 -0600
Add tracking of PurplePerson's to PurpleContactManager
The manager keeps track of all of this internally but exposes the collection of
people via the GListModel interface and exposes add_person and remove_person as
well.
Testing Done:
Ran the unit tests.
Reviewed at https://reviews.imfreedom.org/r/2055/
--- 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 @@ enum { SIG_ADDED, SIG_REMOVED, + SIG_PERSON_ADDED, + SIG_PERSON_REMOVED, N_SIGNALS, }; static guint signals[N_SIGNALS] = {0, }; @@ -35,10 +37,17 @@ GObject parent; GHashTable *accounts; + + GPtrArray *people; }; static PurpleContactManager *default_manager = NULL; +/* Necessary prototype. */ +static void purple_contact_manager_contact_person_changed_cb(GObject *obj, + GParamSpec *pspec, + gpointer data); + /****************************************************************************** * Helpers *****************************************************************************/ @@ -127,9 +136,72 @@ } /****************************************************************************** + * Callbacks + *****************************************************************************/ +static void +purple_contact_manager_contact_person_changed_cb(GObject *obj, + G_GNUC_UNUSED GParamSpec *pspec, + gpointer data) +{ + 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)) { + return; + } + + /* At this point the person changed or is new so we need to add the new + * person. + */ + purple_contact_manager_add_person(manager, person); +} + +/****************************************************************************** + * GListModel Implementation + *****************************************************************************/ +static GType +purple_contact_manager_get_item_type(G_GNUC_UNUSED GListModel *list) { + return PURPLE_TYPE_PERSON; +} + +static guint +purple_contact_manager_get_n_items(GListModel *list) { + PurpleContactManager *manager = PURPLE_CONTACT_MANAGER(list); + + return manager->people->len; +} + +static gpointer +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)); + } + + return contact; +} + +static void +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; +} + +/****************************************************************************** * GObject Implementation *****************************************************************************/ -G_DEFINE_TYPE(PurpleContactManager, purple_contact_manager, G_TYPE_OBJECT) +G_DEFINE_FINAL_TYPE_WITH_CODE(PurpleContactManager, purple_contact_manager, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL, + pidgin_contact_manager_list_model_iface_init)) static void 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 + * list. - gk 20221109 + */ + manager->people = g_ptr_array_new_full(100, + (GDestroyNotify)g_object_unref); } static void @@ -207,6 +290,51 @@ G_TYPE_NONE, 1, PURPLE_TYPE_CONTACT); + + /** + * 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. + * + * Since: 3.0.0 + */ + signals[SIG_PERSON_ADDED] = g_signal_new_class_handler( + "person-added", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST, + NULL, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + PURPLE_TYPE_PERSON); + + /** + * 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. + * + * Since: 3.0.0 + */ + signals[SIG_PERSON_REMOVED] = g_signal_new_class_handler( + "person-removed", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST, + NULL, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + PURPLE_TYPE_PERSON); } /****************************************************************************** @@ -274,6 +402,21 @@ } if(added) { + 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 + * people. + */ + 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), + manager, 0); + 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); } + +void +purple_contact_manager_add_person(PurpleContactManager *manager, + PurplePerson *person) +{ + guint index = 0; + + 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)) { + return; + } + + /* 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); +} + +void +purple_contact_manager_remove_person(PurpleContactManager *manager, + PurplePerson *person, + gboolean remove_contacts) +{ + guint index = 0; + + 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)) { + return; + } + + if(remove_contacts) { + 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 + * reference. + */ + g_object_ref(person); + + 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 @@ G_DEPRECATED 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. + * + * Since: 3.0.0 + */ +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 + * @manager. + * + * Removes @person from @manager optionally removing all of the contacts + * contained in @person as well if @remove_contacts is %TRUE. + * + * Since: 3.0.0 + */ +void purple_contact_manager_remove_person(PurpleContactManager *manager, PurplePerson *person, gboolean remove_contacts); + G_END_DECLS #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); } + +gboolean +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. + * + * Since: 3.0.0 + */ +gboolean purple_person_has_contacts(PurplePerson *person); + G_END_DECLS #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(&buddy); g_clear_object(&contact); } + +/****************************************************************************** + * Person Tests + *****************************************************************************/ +static void +test_purple_contact_manager_person_add_remove(void) { + PurpleContactManager *manager = NULL; + PurplePerson *person = NULL; + GListModel *model = NULL; + int added_called = 0; + 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), + &added_called); + g_signal_connect(manager, "person-removed", + G_CALLBACK(test_purple_contact_manager_increment_cb), + &removed_called); + + /* 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); + + /* Clean up.*/ + g_clear_object(&person); + g_clear_object(&manager); +} + +static void +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), + &person_added_called); + 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); + + /* Clean up.*/ + g_clear_object(&account); + g_clear_object(&contact); + g_clear_object(&person); + g_clear_object(&manager); +} + /****************************************************************************** * Main *****************************************************************************/ @@ -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); + return g_test_run(); }