pidgin/pidgin

3ef645de21e7
Parents 9ad66abc9356
Children 49969fa9a664
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();
}