pidgin/pidgin

Stop automatically adding/remove conversations from the conversation manager

This was done to mimic the old conversations API and shouldn't be needed going
forward. However, since we can't create conversations right now, we're probably
missing some edge cases here.

Also make sure to keep a reference around on the conversation while removing to avoid use after frees.

Testing Done:
Trained with the turtles.

Reviewed at https://reviews.imfreedom.org/r/3049/
/*
* 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
*/
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
*/
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
*/
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);
/* Make sure we have a reference in case we need to emit signals. */
g_object_ref(conversation);
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);
}
g_object_unref(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);
}