Thu, 22 Feb 2024 06:03:16 -0600
Add a favorite property to PurpleContactInfo
This will be used in the future for toggling whether or not contacts are
favorited or starred.
Testing Done:
Ran the unit tests under valgrind.
Reviewed at https://reviews.imfreedom.org/r/2965/
/* * Purple - Internet Messaging Library * Copyright (C) Pidgin Developers <devel@pidgin.im> * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this library; if not, see <https://www.gnu.org/licenses/>. */ #include "purplechatconversation.h" #include "purpleconversationmanager.h" #include "purpleimconversation.h" #include "purpleprivate.h" enum { SIG_REGISTERED, SIG_UNREGISTERED, SIG_CONVERSATION_CHANGED, N_SIGNALS, }; static guint signals[N_SIGNALS] = {0, }; struct _PurpleConversationManager { GObject parent; GHashTable *conversations; }; static PurpleConversationManager *default_manager = NULL; G_DEFINE_FINAL_TYPE(PurpleConversationManager, purple_conversation_manager, G_TYPE_OBJECT) typedef gboolean (*PurpleConversationManagerCompareFunc)(PurpleConversation *conversation, gpointer userdata); /****************************************************************************** * Helpers *****************************************************************************/ static gboolean purple_conversation_is_im(PurpleConversation *conversation, G_GNUC_UNUSED gpointer userdata) { return PURPLE_IS_IM_CONVERSATION(conversation); } static gboolean purple_conversation_is_chat(PurpleConversation *conversation, G_GNUC_UNUSED gpointer userdata) { return PURPLE_IS_CHAT_CONVERSATION(conversation); } static gboolean purple_conversation_chat_has_id(PurpleConversation *conversation, gpointer userdata) { PurpleChatConversation *chat = NULL; gint id = GPOINTER_TO_INT(userdata); if(!PURPLE_IS_CHAT_CONVERSATION(conversation)) { return FALSE; } chat = PURPLE_CHAT_CONVERSATION(conversation); return (purple_chat_conversation_get_id(chat) == id); } static gboolean purple_conversation_has_id(PurpleConversation *conversation, gpointer data) { const char *needle = data; const char *haystack = NULL; if(!PURPLE_IS_CONVERSATION(conversation)) { return FALSE; } haystack = purple_conversation_get_id(conversation); return purple_strequal(needle, haystack); } static PurpleConversation * purple_conversation_manager_find_internal(PurpleConversationManager *manager, PurpleAccount *account, const gchar *name, PurpleConversationManagerCompareFunc func, gpointer userdata) { GHashTableIter iter; gpointer key; g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); g_hash_table_iter_init(&iter, manager->conversations); while(g_hash_table_iter_next(&iter, &key, NULL)) { PurpleConversation *conversation = PURPLE_CONVERSATION(key); if(name != NULL) { const gchar *conv_name = purple_conversation_get_name(conversation); if(!purple_strequal(conv_name, name)) { continue; } } if(purple_conversation_get_account(conversation) != account) { continue; } if(func != NULL && !func(conversation, userdata)) { continue; } return conversation; } return NULL; } /****************************************************************************** * Callbacks *****************************************************************************/ /* This callback propagates the notify signal from conversations. */ static void purple_conversation_manager_conversation_changed_cb(GObject *source, GParamSpec *pspec, gpointer data) { g_signal_emit(data, signals[SIG_CONVERSATION_CHANGED], g_param_spec_get_name_quark(pspec), source, pspec); } /****************************************************************************** * GObject Implementation *****************************************************************************/ static void purple_conversation_manager_init(PurpleConversationManager *manager) { manager->conversations = g_hash_table_new_full(g_direct_hash, g_direct_equal, g_object_unref, NULL); } static void purple_conversation_manager_finalize(GObject *obj) { PurpleConversationManager *manager = PURPLE_CONVERSATION_MANAGER(obj); g_hash_table_destroy(manager->conversations); G_OBJECT_CLASS(purple_conversation_manager_parent_class)->finalize(obj); } static void purple_conversation_manager_class_init(PurpleConversationManagerClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS(klass); obj_class->finalize = purple_conversation_manager_finalize; /** * PurpleConversationManager::registered: * @manager: The manager. * @conversation: The conversation that was registered. * * Emitted after @conversation has been registered with @manager. * * Since: 3.0.0 */ signals[SIG_REGISTERED] = g_signal_new_class_handler( "registered", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_LAST, NULL, NULL, NULL, NULL, G_TYPE_NONE, 1, PURPLE_TYPE_CONVERSATION); /** * PurpleConversationManager::unregistered: * @manager: The manager. * @conversation: The conversation that was unregistered. * * Emitted after @conversation has been unregistered from @manager. * * Since: 3.0.0 */ signals[SIG_UNREGISTERED] = g_signal_new_class_handler( "unregistered", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_LAST, NULL, NULL, NULL, NULL, G_TYPE_NONE, 1, PURPLE_TYPE_CONVERSATION); /** * PurpleConversationManager::conversation-changed: * @manager: The account manager instance. * @conversation: The conversation that was changed. * @pspec: The [class@GObject.ParamSpec] for the property that changed. * * This is a propagation of the notify signal from @conversation. This * means that your callback will be called for any conversation that * @manager knows about. * * This also supports details, so you can specify the signal name as * something like `conversation-changed::title` and your callback will only * be called when [property@Conversation:title] has been changed. * * Since: 3.0.0 */ signals[SIG_CONVERSATION_CHANGED] = g_signal_new_class_handler( "conversation-changed", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, NULL, NULL, NULL, NULL, G_TYPE_NONE, 2, PURPLE_TYPE_CONVERSATION, G_TYPE_PARAM); } /****************************************************************************** * Private API *****************************************************************************/ void purple_conversation_manager_startup(void) { if(default_manager == NULL) { default_manager = g_object_new(PURPLE_TYPE_CONVERSATION_MANAGER, NULL); } } void purple_conversation_manager_shutdown(void) { g_clear_object(&default_manager); } /****************************************************************************** * Public API *****************************************************************************/ PurpleConversationManager * purple_conversation_manager_get_default(void) { return default_manager; } gboolean purple_conversation_manager_register(PurpleConversationManager *manager, PurpleConversation *conversation) { gboolean registered = FALSE; g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), FALSE); g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE); /* g_hash_table_add calls the key_destroy_func if the key already exists * which means we don't need to worry about the reference we're creating * during the addition. */ registered = g_hash_table_add(manager->conversations, g_object_ref(conversation)); if(registered) { /* Register our signals that need to be propagated. */ g_signal_connect_object(conversation, "notify", G_CALLBACK(purple_conversation_manager_conversation_changed_cb), manager, 0); /* Tell everyone about the new conversation. */ g_signal_emit(manager, signals[SIG_REGISTERED], 0, conversation); } return registered; } gboolean purple_conversation_manager_unregister(PurpleConversationManager *manager, PurpleConversation *conversation) { gboolean unregistered = FALSE; g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), FALSE); g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE); unregistered = g_hash_table_remove(manager->conversations, conversation); if(unregistered) { /* Disconnect all the signals we added for propagation. */ g_signal_handlers_disconnect_by_func(conversation, purple_conversation_manager_conversation_changed_cb, manager); /* Tell everyone about the unregistered conversation. */ g_signal_emit(manager, signals[SIG_UNREGISTERED], 0, conversation); } return unregistered; } gboolean purple_conversation_manager_is_registered(PurpleConversationManager *manager, PurpleConversation *conversation) { g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), FALSE); g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE); return g_hash_table_contains(manager->conversations, conversation); } void purple_conversation_manager_foreach(PurpleConversationManager *manager, PurpleConversationManagerForeachFunc func, gpointer data) { GHashTableIter iter; gpointer key; g_return_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager)); g_return_if_fail(func != NULL); g_hash_table_iter_init(&iter, manager->conversations); while(g_hash_table_iter_next(&iter, &key, NULL)) { func(PURPLE_CONVERSATION(key), data); } } GList * purple_conversation_manager_get_all(PurpleConversationManager *manager) { g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL); return g_hash_table_get_keys(manager->conversations); } PurpleConversation * purple_conversation_manager_find(PurpleConversationManager *manager, PurpleAccount *account, const gchar *name) { g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL); g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); g_return_val_if_fail(name != NULL, NULL); return purple_conversation_manager_find_internal(manager, account, name, NULL, NULL); } PurpleConversation * purple_conversation_manager_find_im(PurpleConversationManager *manager, PurpleAccount *account, const gchar *name) { g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL); g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); g_return_val_if_fail(name != NULL, NULL); return purple_conversation_manager_find_internal(manager, account, name, purple_conversation_is_im, NULL); } PurpleConversation * purple_conversation_manager_find_chat(PurpleConversationManager *manager, PurpleAccount *account, const gchar *name) { g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL); g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); g_return_val_if_fail(name != NULL, NULL); return purple_conversation_manager_find_internal(manager, account, name, purple_conversation_is_chat, NULL); } PurpleConversation * purple_conversation_manager_find_chat_by_id(PurpleConversationManager *manager, PurpleAccount *account, gint id) { g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL); g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); return purple_conversation_manager_find_internal(manager, account, NULL, purple_conversation_chat_has_id, GINT_TO_POINTER(id)); } PurpleConversation * purple_conversation_manager_find_with_id(PurpleConversationManager *manager, PurpleAccount *account, const char *id) { g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL); g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); return purple_conversation_manager_find_internal(manager, account, NULL, purple_conversation_has_id, (gpointer)id); }