pidgin/pidgin

closing merged branch
memory-leaks
2020-02-03, Gary Kramlich
208e387899a5
closing merged branch
/*
* purple
*
* 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 program 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 program 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*
*/
#include "internal.h"
#include "buddylist.h"
#include "conversation.h"
#include "debug.h"
#include "notify.h"
#include "pounce.h"
#include "prefs.h"
#include "protocol.h"
#include "server.h"
#include "signals.h"
#include "util.h"
#include "xmlnode.h"
/* Private data for a buddy list. */
typedef struct {
PurpleBlistNode *root;
GHashTable *buddies; /* Every buddy in this list */
} PurpleBuddyListPrivate;
static GType buddy_list_type = G_TYPE_INVALID;
static PurpleBuddyList *purplebuddylist = NULL;
G_DEFINE_TYPE_WITH_PRIVATE(PurpleBuddyList, purple_buddy_list, G_TYPE_OBJECT);
/*
* A hash table used for efficient lookups of buddies by name.
* PurpleAccount* => GHashTable*, with the inner hash table being
* struct _purple_hbuddy => PurpleBuddy*
*/
static GHashTable *buddies_cache = NULL;
/*
* A hash table used for efficient lookups of groups by name.
* UTF-8 collate-key => PurpleGroup*.
*/
static GHashTable *groups_cache = NULL;
static guint save_timer = 0;
static gboolean blist_loaded = FALSE;
static gchar *localized_default_group_name = NULL;
/*********************************************************************
* Private utility functions *
*********************************************************************/
static gchar *
purple_blist_fold_name(const gchar *name)
{
gchar *res, *tmp;
if (name == NULL)
return NULL;
tmp = g_utf8_casefold(name, -1);
res = g_utf8_collate_key(tmp, -1);
g_free(tmp);
return res;
}
static PurpleBlistNode *purple_blist_get_last_sibling(PurpleBlistNode *node)
{
PurpleBlistNode *n = node;
if (!n)
return NULL;
while (n->next)
n = n->next;
return n;
}
PurpleBlistNode *_purple_blist_get_last_child(PurpleBlistNode *node)
{
if (!node)
return NULL;
return purple_blist_get_last_sibling(node->child);
}
struct _list_account_buddies {
GSList *list;
PurpleAccount *account;
};
struct _purple_hbuddy {
char *name;
PurpleAccount *account;
PurpleBlistNode *group;
};
/* This function must not use purple_normalize */
static guint _purple_blist_hbuddy_hash(struct _purple_hbuddy *hb)
{
return g_str_hash(hb->name) ^ g_direct_hash(hb->group) ^ g_direct_hash(hb->account);
}
/* This function must not use purple_normalize */
static guint _purple_blist_hbuddy_equal(struct _purple_hbuddy *hb1, struct _purple_hbuddy *hb2)
{
return (hb1->group == hb2->group &&
hb1->account == hb2->account &&
purple_strequal(hb1->name, hb2->name));
}
static void _purple_blist_hbuddy_free_key(struct _purple_hbuddy *hb)
{
g_free(hb->name);
g_free(hb);
}
static void
purple_blist_buddies_cache_add_account(PurpleAccount *account)
{
GHashTable *account_buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash,
(GEqualFunc)_purple_blist_hbuddy_equal,
(GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
g_hash_table_insert(buddies_cache, account, account_buddies);
}
static void
purple_blist_buddies_cache_remove_account(const PurpleAccount *account)
{
g_hash_table_remove(buddies_cache, account);
}
/*********************************************************************
* Writing to disk *
*********************************************************************/
static void
value_to_xmlnode(gpointer key, gpointer hvalue, gpointer user_data)
{
const char *name;
GValue *value;
PurpleXmlNode *node, *child;
char buf[21];
name = (const char *)key;
value = (GValue *)hvalue;
node = (PurpleXmlNode *)user_data;
g_return_if_fail(value != NULL);
child = purple_xmlnode_new_child(node, "setting");
purple_xmlnode_set_attrib(child, "name", name);
if (G_VALUE_HOLDS_INT(value)) {
purple_xmlnode_set_attrib(child, "type", "int");
g_snprintf(buf, sizeof(buf), "%d", g_value_get_int(value));
purple_xmlnode_insert_data(child, buf, -1);
}
else if (G_VALUE_HOLDS_STRING(value)) {
purple_xmlnode_set_attrib(child, "type", "string");
purple_xmlnode_insert_data(child, g_value_get_string(value), -1);
}
else if (G_VALUE_HOLDS_BOOLEAN(value)) {
purple_xmlnode_set_attrib(child, "type", "bool");
g_snprintf(buf, sizeof(buf), "%d", g_value_get_boolean(value));
purple_xmlnode_insert_data(child, buf, -1);
}
}
static void
chat_component_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
{
const char *name;
const char *data;
PurpleXmlNode *node, *child;
name = (const char *)key;
data = (const char *)value;
node = (PurpleXmlNode *)user_data;
g_return_if_fail(data != NULL);
child = purple_xmlnode_new_child(node, "component");
purple_xmlnode_set_attrib(child, "name", name);
purple_xmlnode_insert_data(child, data, -1);
}
static PurpleXmlNode *
buddy_to_xmlnode(PurpleBuddy *buddy)
{
PurpleXmlNode *node, *child;
PurpleAccount *account = purple_buddy_get_account(buddy);
const char *alias = purple_buddy_get_local_alias(buddy);
node = purple_xmlnode_new("buddy");
purple_xmlnode_set_attrib(node, "account", purple_account_get_username(account));
purple_xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
child = purple_xmlnode_new_child(node, "name");
purple_xmlnode_insert_data(child, purple_buddy_get_name(buddy), -1);
if (alias != NULL)
{
child = purple_xmlnode_new_child(node, "alias");
purple_xmlnode_insert_data(child, alias, -1);
}
/* Write buddy settings */
g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(buddy)),
value_to_xmlnode, node);
return node;
}
static PurpleXmlNode *
contact_to_xmlnode(PurpleContact *contact)
{
PurpleXmlNode *node, *child;
PurpleBlistNode *bnode;
gchar *alias;
node = purple_xmlnode_new("contact");
g_object_get(contact, "alias", &alias, NULL);
if (alias != NULL)
{
purple_xmlnode_set_attrib(node, "alias", alias);
}
/* Write buddies */
for (bnode = PURPLE_BLIST_NODE(contact)->child; bnode != NULL; bnode = bnode->next)
{
if (purple_blist_node_is_transient(bnode))
continue;
if (PURPLE_IS_BUDDY(bnode))
{
child = buddy_to_xmlnode(PURPLE_BUDDY(bnode));
purple_xmlnode_insert_child(node, child);
}
}
/* Write contact settings */
g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(contact)),
value_to_xmlnode, node);
g_free(alias);
return node;
}
static PurpleXmlNode *
chat_to_xmlnode(PurpleChat *chat)
{
PurpleXmlNode *node, *child;
PurpleAccount *account = purple_chat_get_account(chat);
gchar *alias;
g_object_get(chat, "alias", &alias, NULL);
node = purple_xmlnode_new("chat");
purple_xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
purple_xmlnode_set_attrib(node, "account", purple_account_get_username(account));
if (alias != NULL)
{
child = purple_xmlnode_new_child(node, "alias");
purple_xmlnode_insert_data(child, alias, -1);
}
/* Write chat components */
g_hash_table_foreach(purple_chat_get_components(chat),
chat_component_to_xmlnode, node);
/* Write chat settings */
g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(chat)),
value_to_xmlnode, node);
g_free(alias);
return node;
}
static PurpleXmlNode *
group_to_xmlnode(PurpleGroup *group)
{
PurpleXmlNode *node, *child;
PurpleBlistNode *cnode;
node = purple_xmlnode_new("group");
if (group != purple_blist_get_default_group())
purple_xmlnode_set_attrib(node, "name", purple_group_get_name(group));
/* Write settings */
g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(group)),
value_to_xmlnode, node);
/* Write contacts and chats */
for (cnode = PURPLE_BLIST_NODE(group)->child; cnode != NULL; cnode = cnode->next)
{
if (purple_blist_node_is_transient(cnode))
continue;
if (PURPLE_IS_CONTACT(cnode))
{
child = contact_to_xmlnode(PURPLE_CONTACT(cnode));
purple_xmlnode_insert_child(node, child);
}
else if (PURPLE_IS_CHAT(cnode))
{
child = chat_to_xmlnode(PURPLE_CHAT(cnode));
purple_xmlnode_insert_child(node, child);
}
}
return node;
}
static PurpleXmlNode *
accountprivacy_to_xmlnode(PurpleAccount *account)
{
PurpleXmlNode *node, *child;
GSList *cur;
char buf[10];
node = purple_xmlnode_new("account");
purple_xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
purple_xmlnode_set_attrib(node, "name", purple_account_get_username(account));
g_snprintf(buf, sizeof(buf), "%d", purple_account_get_privacy_type(account));
purple_xmlnode_set_attrib(node, "mode", buf);
for (cur = purple_account_privacy_get_permitted(account); cur; cur = cur->next)
{
child = purple_xmlnode_new_child(node, "permit");
purple_xmlnode_insert_data(child, cur->data, -1);
}
for (cur = purple_account_privacy_get_denied(account); cur; cur = cur->next)
{
child = purple_xmlnode_new_child(node, "block");
purple_xmlnode_insert_data(child, cur->data, -1);
}
return node;
}
static PurpleXmlNode *
blist_to_xmlnode(void)
{
PurpleXmlNode *node, *child, *grandchild;
PurpleBlistNode *gnode;
GList *cur;
const gchar *localized_default;
node = purple_xmlnode_new("purple");
purple_xmlnode_set_attrib(node, "version", "1.0");
/* Write groups */
child = purple_xmlnode_new_child(node, "blist");
localized_default = localized_default_group_name;
if (!purple_strequal(_("Buddies"), "Buddies"))
localized_default = _("Buddies");
if (localized_default != NULL) {
purple_xmlnode_set_attrib(child,
"localized-default-group", localized_default);
}
for (gnode = purple_blist_get_default_root(); gnode != NULL;
gnode = gnode->next) {
if (purple_blist_node_is_transient(gnode))
continue;
if (PURPLE_IS_GROUP(gnode))
{
grandchild = group_to_xmlnode(PURPLE_GROUP(gnode));
purple_xmlnode_insert_child(child, grandchild);
}
}
/* Write privacy settings */
child = purple_xmlnode_new_child(node, "privacy");
for (cur = purple_accounts_get_all(); cur != NULL; cur = cur->next)
{
grandchild = accountprivacy_to_xmlnode(cur->data);
purple_xmlnode_insert_child(child, grandchild);
}
return node;
}
static void
purple_blist_sync(void)
{
PurpleXmlNode *node;
char *data;
if (!blist_loaded)
{
purple_debug_error("buddylist", "Attempted to save buddy list before it "
"was read!\n");
return;
}
node = blist_to_xmlnode();
data = purple_xmlnode_to_formatted_str(node, NULL);
purple_util_write_data_to_config_file("blist.xml", data, -1);
g_free(data);
purple_xmlnode_free(node);
}
static gboolean
save_cb(gpointer data)
{
purple_blist_sync();
save_timer = 0;
return FALSE;
}
static void
purple_blist_real_schedule_save(void)
{
if (save_timer == 0)
save_timer = g_timeout_add_seconds(5, save_cb, NULL);
}
static void
purple_blist_real_save_account(PurpleBuddyList *list, PurpleAccount *account)
{
#if 1
purple_blist_real_schedule_save();
#else
if (account != NULL) {
/* Save the buddies and privacy data for this account */
} else {
/* Save all buddies and privacy data */
}
#endif
}
static void
purple_blist_real_save_node(PurpleBuddyList *list, PurpleBlistNode *node)
{
purple_blist_real_schedule_save();
}
void purple_blist_schedule_save()
{
PurpleBuddyListClass *klass = NULL;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
/* Save everything */
if (klass && klass->save_account) {
klass->save_account(purplebuddylist, NULL);
}
}
/*********************************************************************
* Reading from disk *
*********************************************************************/
static void
parse_setting(PurpleBlistNode *node, PurpleXmlNode *setting)
{
const char *name = purple_xmlnode_get_attrib(setting, "name");
const char *type = purple_xmlnode_get_attrib(setting, "type");
char *value = purple_xmlnode_get_data(setting);
if (!value)
return;
if (!type || purple_strequal(type, "string"))
purple_blist_node_set_string(node, name, value);
else if (purple_strequal(type, "bool"))
purple_blist_node_set_bool(node, name, atoi(value));
else if (purple_strequal(type, "int"))
purple_blist_node_set_int(node, name, atoi(value));
g_free(value);
}
static void
parse_buddy(PurpleGroup *group, PurpleContact *contact, PurpleXmlNode *bnode)
{
PurpleAccount *account;
PurpleBuddy *buddy;
char *name = NULL, *alias = NULL;
const char *acct_name, *proto;
PurpleXmlNode *x;
acct_name = purple_xmlnode_get_attrib(bnode, "account");
proto = purple_xmlnode_get_attrib(bnode, "proto");
if (!acct_name || !proto)
return;
account = purple_accounts_find(acct_name, proto);
if (!account)
return;
if ((x = purple_xmlnode_get_child(bnode, "name")))
name = purple_xmlnode_get_data(x);
if (!name)
return;
if ((x = purple_xmlnode_get_child(bnode, "alias")))
alias = purple_xmlnode_get_data(x);
buddy = purple_buddy_new(account, name, alias);
purple_blist_add_buddy(buddy, contact, group,
_purple_blist_get_last_child((PurpleBlistNode*)contact));
for (x = purple_xmlnode_get_child(bnode, "setting"); x; x = purple_xmlnode_get_next_twin(x)) {
parse_setting((PurpleBlistNode*)buddy, x);
}
g_free(name);
g_free(alias);
}
static void
parse_contact(PurpleGroup *group, PurpleXmlNode *cnode)
{
PurpleContact *contact = purple_contact_new();
PurpleXmlNode *x;
const char *alias;
purple_blist_add_contact(contact, group,
_purple_blist_get_last_child((PurpleBlistNode*)group));
if ((alias = purple_xmlnode_get_attrib(cnode, "alias"))) {
purple_contact_set_alias(contact, alias);
}
for (x = cnode->child; x; x = x->next) {
if (x->type != PURPLE_XMLNODE_TYPE_TAG)
continue;
if (purple_strequal(x->name, "buddy"))
parse_buddy(group, contact, x);
else if (purple_strequal(x->name, "setting"))
parse_setting(PURPLE_BLIST_NODE(contact), x);
}
/* if the contact is empty, don't keep it around. it causes problems */
if (!PURPLE_BLIST_NODE(contact)->child)
purple_blist_remove_contact(contact);
}
static void
parse_chat(PurpleGroup *group, PurpleXmlNode *cnode)
{
PurpleChat *chat;
PurpleAccount *account;
const char *acct_name, *proto;
PurpleXmlNode *x;
char *alias = NULL;
GHashTable *components;
acct_name = purple_xmlnode_get_attrib(cnode, "account");
proto = purple_xmlnode_get_attrib(cnode, "proto");
if (!acct_name || !proto)
return;
account = purple_accounts_find(acct_name, proto);
if (!account)
return;
if ((x = purple_xmlnode_get_child(cnode, "alias")))
alias = purple_xmlnode_get_data(x);
components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
for (x = purple_xmlnode_get_child(cnode, "component"); x; x = purple_xmlnode_get_next_twin(x)) {
const char *name;
char *value;
name = purple_xmlnode_get_attrib(x, "name");
value = purple_xmlnode_get_data(x);
g_hash_table_replace(components, g_strdup(name), value);
}
chat = purple_chat_new(account, alias, components);
purple_blist_add_chat(chat, group,
_purple_blist_get_last_child((PurpleBlistNode*)group));
for (x = purple_xmlnode_get_child(cnode, "setting"); x; x = purple_xmlnode_get_next_twin(x)) {
parse_setting((PurpleBlistNode*)chat, x);
}
g_free(alias);
}
static void
parse_group(PurpleXmlNode *groupnode)
{
const char *name = purple_xmlnode_get_attrib(groupnode, "name");
PurpleGroup *group;
PurpleXmlNode *cnode;
group = purple_group_new(name);
purple_blist_add_group(group, purple_blist_get_last_sibling(
purple_blist_get_default_root()));
for (cnode = groupnode->child; cnode; cnode = cnode->next) {
if (cnode->type != PURPLE_XMLNODE_TYPE_TAG)
continue;
if (purple_strequal(cnode->name, "setting"))
parse_setting((PurpleBlistNode*)group, cnode);
else if (purple_strequal(cnode->name, "contact") ||
purple_strequal(cnode->name, "person"))
parse_contact(group, cnode);
else if (purple_strequal(cnode->name, "chat"))
parse_chat(group, cnode);
}
}
static void
load_blist(void)
{
PurpleXmlNode *purple, *blist, *privacy;
blist_loaded = TRUE;
purple = purple_util_read_xml_from_config_file("blist.xml", _("buddy list"));
if (purple == NULL)
return;
blist = purple_xmlnode_get_child(purple, "blist");
if (blist) {
PurpleXmlNode *groupnode;
localized_default_group_name = g_strdup(
purple_xmlnode_get_attrib(blist,
"localized-default-group"));
for (groupnode = purple_xmlnode_get_child(blist, "group"); groupnode != NULL;
groupnode = purple_xmlnode_get_next_twin(groupnode)) {
parse_group(groupnode);
}
} else {
g_free(localized_default_group_name);
localized_default_group_name = NULL;
}
privacy = purple_xmlnode_get_child(purple, "privacy");
if (privacy) {
PurpleXmlNode *anode;
for (anode = privacy->child; anode; anode = anode->next) {
PurpleXmlNode *x;
PurpleAccount *account;
int imode;
const char *acct_name, *proto, *mode;
acct_name = purple_xmlnode_get_attrib(anode, "name");
proto = purple_xmlnode_get_attrib(anode, "proto");
mode = purple_xmlnode_get_attrib(anode, "mode");
if (!acct_name || !proto || !mode)
continue;
account = purple_accounts_find(acct_name, proto);
if (!account)
continue;
imode = atoi(mode);
purple_account_set_privacy_type(account, (imode != 0 ? imode : PURPLE_ACCOUNT_PRIVACY_ALLOW_ALL));
for (x = anode->child; x; x = x->next) {
char *name;
if (x->type != PURPLE_XMLNODE_TYPE_TAG)
continue;
if (purple_strequal(x->name, "permit")) {
name = purple_xmlnode_get_data(x);
purple_account_privacy_permit_add(account, name, TRUE);
g_free(name);
} else if (purple_strequal(x->name, "block")) {
name = purple_xmlnode_get_data(x);
purple_account_privacy_deny_add(account, name, TRUE);
g_free(name);
}
}
}
}
purple_xmlnode_free(purple);
/* This tells the buddy icon code to do its thing. */
_purple_buddy_icons_blist_loaded_cb();
}
/*****************************************************************************
* Public API functions *
*****************************************************************************/
void
purple_blist_set_ui(GType type)
{
g_return_if_fail(g_type_is_a(type, PURPLE_TYPE_BUDDY_LIST) ||
type == G_TYPE_INVALID);
buddy_list_type = type;
}
void
purple_blist_boot(void)
{
GList *account;
PurpleBuddyList *gbl = g_object_new(buddy_list_type, NULL);
buddies_cache = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, (GDestroyNotify)g_hash_table_destroy);
groups_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
for (account = purple_accounts_get_all(); account != NULL; account = account->next)
{
purple_blist_buddies_cache_add_account(account->data);
}
purplebuddylist = gbl;
load_blist();
}
PurpleBuddyList *
purple_blist_get_default(void)
{
return purplebuddylist;
}
PurpleBlistNode *
purple_blist_get_default_root(void)
{
if (purplebuddylist) {
PurpleBuddyListPrivate *priv =
purple_buddy_list_get_instance_private(purplebuddylist);
return priv->root;
}
return NULL;
}
PurpleBlistNode *
purple_blist_get_root(PurpleBuddyList *list)
{
PurpleBuddyListPrivate *priv = NULL;
g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(list), NULL);
priv = purple_buddy_list_get_instance_private(list);
return priv->root;
}
static void
append_buddy(gpointer key, gpointer value, gpointer user_data)
{
GSList **list = user_data;
*list = g_slist_prepend(*list, value);
}
GSList *
purple_blist_get_buddies()
{
PurpleBuddyListPrivate *priv;
GSList *buddies = NULL;
if (!purplebuddylist)
return NULL;
priv = purple_buddy_list_get_instance_private(purplebuddylist);
g_hash_table_foreach(priv->buddies, append_buddy, &buddies);
return buddies;
}
void purple_blist_show()
{
PurpleBuddyListClass *klass = NULL;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
if (klass && klass->show) {
klass->show(purplebuddylist);
}
}
void purple_blist_set_visible(gboolean show)
{
PurpleBuddyListClass *klass = NULL;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
if (klass && klass->set_visible) {
klass->set_visible(purplebuddylist, show);
}
}
void purple_blist_update_buddies_cache(PurpleBuddy *buddy, const char *new_name)
{
struct _purple_hbuddy *hb, *hb2;
GHashTable *account_buddies;
PurpleAccount *account;
gchar *name;
PurpleBuddyListPrivate *priv =
purple_buddy_list_get_instance_private(purplebuddylist);
g_return_if_fail(PURPLE_IS_BUDDY(buddy));
account = purple_buddy_get_account(buddy);
name = (gchar *)purple_buddy_get_name(buddy);
hb = g_new(struct _purple_hbuddy, 1);
hb->name = (gchar *)purple_normalize(account, name);
hb->account = account;
hb->group = PURPLE_BLIST_NODE(buddy)->parent->parent;
g_hash_table_remove(priv->buddies, hb);
account_buddies = g_hash_table_lookup(buddies_cache, account);
g_hash_table_remove(account_buddies, hb);
hb->name = g_strdup(purple_normalize(account, new_name));
g_hash_table_replace(priv->buddies, hb, buddy);
hb2 = g_new(struct _purple_hbuddy, 1);
hb2->name = g_strdup(hb->name);
hb2->account = account;
hb2->group = PURPLE_BLIST_NODE(buddy)->parent->parent;
g_hash_table_replace(account_buddies, hb2, buddy);
}
void purple_blist_update_groups_cache(PurpleGroup *group, const char *new_name)
{
gchar* key;
key = purple_blist_fold_name(purple_group_get_name(group));
g_hash_table_remove(groups_cache, key);
g_free(key);
g_hash_table_insert(groups_cache,
purple_blist_fold_name(new_name), group);
}
void purple_blist_add_chat(PurpleChat *chat, PurpleGroup *group, PurpleBlistNode *node)
{
PurpleBlistNode *cnode = PURPLE_BLIST_NODE(chat);
PurpleBuddyListClass *klass = NULL;
PurpleCountingNode *group_counter;
g_return_if_fail(PURPLE_IS_CHAT(chat));
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
if (node == NULL) {
if (group == NULL)
group = purple_group_new(_("Chats"));
/* Add group to blist if isn't already on it. Fixes #2752. */
if (!purple_blist_find_group(purple_group_get_name(group))) {
purple_blist_add_group(
group,
purple_blist_get_last_sibling(
purple_blist_get_default_root()));
}
} else {
group = PURPLE_GROUP(node->parent);
}
/* if we're moving to overtop of ourselves, do nothing */
if (cnode == node)
return;
if (cnode->parent) {
/* This chat was already in the list and is
* being moved.
*/
group_counter = PURPLE_COUNTING_NODE(cnode->parent);
purple_counting_node_change_total_size(group_counter, -1);
if (purple_account_is_connected(purple_chat_get_account(chat))) {
purple_counting_node_change_online_count(group_counter, -1);
purple_counting_node_change_current_size(group_counter, -1);
}
if (cnode->next)
cnode->next->prev = cnode->prev;
if (cnode->prev)
cnode->prev->next = cnode->next;
if (cnode->parent->child == cnode)
cnode->parent->child = cnode->next;
if (klass && klass->remove) {
klass->remove(purplebuddylist, cnode);
}
/* ops->remove() cleaned up the cnode's ui_data, so we need to
* reinitialize it */
if (klass && klass->new_node) {
klass->new_node(purplebuddylist, cnode);
}
}
if (node != NULL) {
if (node->next)
node->next->prev = cnode;
cnode->next = node->next;
cnode->prev = node;
cnode->parent = node->parent;
node->next = cnode;
group_counter = PURPLE_COUNTING_NODE(node->parent);
purple_counting_node_change_total_size(group_counter, +1);
if (purple_account_is_connected(purple_chat_get_account(chat))) {
purple_counting_node_change_online_count(group_counter, +1);
purple_counting_node_change_current_size(group_counter, +1);
}
} else {
if (((PurpleBlistNode *)group)->child)
((PurpleBlistNode *)group)->child->prev = cnode;
cnode->next = ((PurpleBlistNode *)group)->child;
cnode->prev = NULL;
((PurpleBlistNode *)group)->child = cnode;
cnode->parent = PURPLE_BLIST_NODE(group);
group_counter = PURPLE_COUNTING_NODE(group);
purple_counting_node_change_total_size(group_counter, +1);
if (purple_account_is_connected(purple_chat_get_account(chat))) {
purple_counting_node_change_online_count(group_counter, +1);
purple_counting_node_change_current_size(group_counter, +1);
}
}
if (klass) {
if (klass->save_node) {
klass->save_node(purplebuddylist, cnode);
}
if (klass->update) {
klass->update(purplebuddylist,
PURPLE_BLIST_NODE(cnode));
}
}
purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
cnode);
}
void purple_blist_add_buddy(PurpleBuddy *buddy, PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
{
PurpleBuddyListClass *klass = NULL;
PurpleBuddyListPrivate *priv = NULL;
PurpleBlistNode *cnode, *bnode;
PurpleCountingNode *contact_counter, *group_counter;
PurpleGroup *g;
PurpleContact *c;
PurpleAccount *account;
struct _purple_hbuddy *hb, *hb2;
GHashTable *account_buddies;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
g_return_if_fail(PURPLE_IS_BUDDY(buddy));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
priv = purple_buddy_list_get_instance_private(purplebuddylist);
bnode = PURPLE_BLIST_NODE(buddy);
account = purple_buddy_get_account(buddy);
/* if we're moving to overtop of ourselves, do nothing */
if (bnode == node || (!node && bnode->parent &&
contact && bnode->parent == (PurpleBlistNode*)contact
&& bnode == bnode->parent->child))
return;
if (node && PURPLE_IS_BUDDY(node)) {
c = (PurpleContact*)node->parent;
g = (PurpleGroup*)node->parent->parent;
} else if (contact) {
c = contact;
g = PURPLE_GROUP(PURPLE_BLIST_NODE(c)->parent);
} else {
g = group;
if (g == NULL)
g = purple_blist_get_default_group();
/* Add group to blist if isn't already on it. Fixes #2752. */
if (!purple_blist_find_group(purple_group_get_name(g))) {
purple_blist_add_group(
g, purple_blist_get_last_sibling(priv->root));
}
c = purple_contact_new();
purple_blist_add_contact(c, g,
_purple_blist_get_last_child((PurpleBlistNode*)g));
}
cnode = PURPLE_BLIST_NODE(c);
if (bnode->parent) {
contact_counter = PURPLE_COUNTING_NODE(bnode->parent);
group_counter = PURPLE_COUNTING_NODE(bnode->parent->parent);
if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
purple_counting_node_change_online_count(contact_counter, -1);
if (purple_counting_node_get_online_count(contact_counter) == 0)
purple_counting_node_change_online_count(group_counter, -1);
}
if (purple_account_is_connected(account)) {
purple_counting_node_change_current_size(contact_counter, -1);
if (purple_counting_node_get_current_size(contact_counter) == 0)
purple_counting_node_change_current_size(group_counter, -1);
}
purple_counting_node_change_total_size(contact_counter, -1);
/* the group totalsize will be taken care of by remove_contact below */
if (bnode->parent->parent != (PurpleBlistNode*)g) {
purple_signal_emit(purple_blist_get_handle(), "buddy-removed-from-group", buddy);
purple_serv_move_buddy(buddy, (PurpleGroup *)bnode->parent->parent, g);
}
if (bnode->next)
bnode->next->prev = bnode->prev;
if (bnode->prev)
bnode->prev->next = bnode->next;
if (bnode->parent->child == bnode)
bnode->parent->child = bnode->next;
if (klass && klass->remove) {
klass->remove(purplebuddylist, bnode);
}
if (bnode->parent->parent != (PurpleBlistNode*)g) {
struct _purple_hbuddy hb;
hb.name = (gchar *)purple_normalize(account,
purple_buddy_get_name(buddy));
hb.account = account;
hb.group = bnode->parent->parent;
g_hash_table_remove(priv->buddies, &hb);
account_buddies = g_hash_table_lookup(buddies_cache, account);
g_hash_table_remove(account_buddies, &hb);
}
if (!bnode->parent->child) {
purple_blist_remove_contact((PurpleContact*)bnode->parent);
} else {
purple_contact_invalidate_priority_buddy((PurpleContact*)bnode->parent);
if (klass && klass->update) {
klass->update(purplebuddylist, bnode->parent);
}
}
}
if (node && PURPLE_IS_BUDDY(node)) {
if (node->next)
node->next->prev = bnode;
bnode->next = node->next;
bnode->prev = node;
bnode->parent = node->parent;
node->next = bnode;
} else {
if (cnode->child)
cnode->child->prev = bnode;
bnode->prev = NULL;
bnode->next = cnode->child;
cnode->child = bnode;
bnode->parent = cnode;
}
contact_counter = PURPLE_COUNTING_NODE(bnode->parent);
group_counter = PURPLE_COUNTING_NODE(bnode->parent->parent);
if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
purple_counting_node_change_online_count(contact_counter, +1);
if (purple_counting_node_get_online_count(contact_counter) == 1)
purple_counting_node_change_online_count(group_counter, +1);
}
if (purple_account_is_connected(account)) {
purple_counting_node_change_current_size(contact_counter, +1);
if (purple_counting_node_get_current_size(contact_counter) == 1)
purple_counting_node_change_current_size(group_counter, +1);
}
purple_counting_node_change_total_size(contact_counter, +1);
hb = g_new(struct _purple_hbuddy, 1);
hb->name = g_strdup(purple_normalize(account, purple_buddy_get_name(buddy)));
hb->account = account;
hb->group = PURPLE_BLIST_NODE(buddy)->parent->parent;
g_hash_table_replace(priv->buddies, hb, buddy);
account_buddies = g_hash_table_lookup(buddies_cache, account);
hb2 = g_new(struct _purple_hbuddy, 1);
hb2->name = g_strdup(hb->name);
hb2->account = account;
hb2->group = ((PurpleBlistNode*)buddy)->parent->parent;
g_hash_table_replace(account_buddies, hb2, buddy);
purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy));
if (klass) {
if (klass->save_node) {
klass->save_node(purplebuddylist,
(PurpleBlistNode *)buddy);
}
if (klass->update) {
klass->update(purplebuddylist,
PURPLE_BLIST_NODE(buddy));
}
}
/* Signal that the buddy has been added */
purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
PURPLE_BLIST_NODE(buddy));
}
void purple_blist_add_contact(PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
{
PurpleBuddyListClass *klass = NULL;
PurpleBuddyListPrivate *priv = NULL;
PurpleGroup *g;
PurpleBlistNode *gnode, *cnode, *bnode;
PurpleCountingNode *contact_counter, *group_counter;
g_return_if_fail(PURPLE_IS_CONTACT(contact));
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
if (PURPLE_BLIST_NODE(contact) == node)
return;
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
priv = purple_buddy_list_get_instance_private(purplebuddylist);
if (node && (PURPLE_IS_CONTACT(node) ||
PURPLE_IS_CHAT(node)))
g = PURPLE_GROUP(node->parent);
else if (group)
g = group;
else
g = purple_blist_get_default_group();
gnode = (PurpleBlistNode*)g;
cnode = (PurpleBlistNode*)contact;
if (cnode->parent) {
if (cnode->parent->child == cnode)
cnode->parent->child = cnode->next;
if (cnode->prev)
cnode->prev->next = cnode->next;
if (cnode->next)
cnode->next->prev = cnode->prev;
if (cnode->parent != gnode) {
bnode = cnode->child;
while (bnode) {
PurpleBlistNode *next_bnode = bnode->next;
PurpleBuddy *b = PURPLE_BUDDY(bnode);
PurpleAccount *account = purple_buddy_get_account(b);
GHashTable *account_buddies;
struct _purple_hbuddy *hb, *hb2;
hb = g_new(struct _purple_hbuddy, 1);
hb->name = g_strdup(purple_normalize(account, purple_buddy_get_name(b)));
hb->account = account;
hb->group = cnode->parent;
g_hash_table_remove(priv->buddies, hb);
account_buddies = g_hash_table_lookup(buddies_cache, account);
g_hash_table_remove(account_buddies, hb);
if (!purple_blist_find_buddy_in_group(account, purple_buddy_get_name(b), g)) {
hb->group = gnode;
g_hash_table_replace(priv->buddies, hb, b);
hb2 = g_new(struct _purple_hbuddy, 1);
hb2->name = g_strdup(hb->name);
hb2->account = account;
hb2->group = gnode;
g_hash_table_replace(account_buddies, hb2, b);
if (purple_account_get_connection(account))
purple_serv_move_buddy(b, (PurpleGroup *)cnode->parent, g);
} else {
gboolean empty_contact = FALSE;
/* this buddy already exists in the group, so we're
* gonna delete it instead */
g_free(hb->name);
g_free(hb);
if (purple_account_get_connection(account))
purple_account_remove_buddy(account, b, PURPLE_GROUP(cnode->parent));
if (!cnode->child->next)
empty_contact = TRUE;
purple_blist_remove_buddy(b);
/* in purple_blist_remove_buddy(), if the last buddy in a
* contact is removed, the contact is cleaned up and
* g_free'd, so we mustn't try to reference bnode->next */
if (empty_contact)
return;
}
bnode = next_bnode;
}
}
contact_counter = PURPLE_COUNTING_NODE(contact);
group_counter = PURPLE_COUNTING_NODE(cnode->parent);
if (purple_counting_node_get_online_count(contact_counter) > 0)
purple_counting_node_change_online_count(group_counter, -1);
if (purple_counting_node_get_current_size(contact_counter) > 0)
purple_counting_node_change_current_size(group_counter, -1);
purple_counting_node_change_total_size(group_counter, -1);
if (klass && klass->remove) {
klass->remove(purplebuddylist, cnode);
}
if (klass && klass->remove_node) {
klass->remove_node(purplebuddylist, cnode);
}
}
if (node && (PURPLE_IS_CONTACT(node) ||
PURPLE_IS_CHAT(node))) {
if (node->next)
node->next->prev = cnode;
cnode->next = node->next;
cnode->prev = node;
cnode->parent = node->parent;
node->next = cnode;
} else {
if (gnode->child)
gnode->child->prev = cnode;
cnode->prev = NULL;
cnode->next = gnode->child;
gnode->child = cnode;
cnode->parent = gnode;
}
contact_counter = PURPLE_COUNTING_NODE(contact);
group_counter = PURPLE_COUNTING_NODE(g);
if (purple_counting_node_get_online_count(contact_counter) > 0)
purple_counting_node_change_online_count(group_counter, +1);
if (purple_counting_node_get_current_size(contact_counter) > 0)
purple_counting_node_change_current_size(group_counter, +1);
purple_counting_node_change_total_size(group_counter, +1);
if (klass && klass->save_node) {
if (cnode->child) {
klass->save_node(purplebuddylist, cnode);
}
for (bnode = cnode->child; bnode; bnode = bnode->next) {
klass->save_node(purplebuddylist, bnode);
}
}
if (klass && klass->update) {
if (cnode->child) {
klass->update(purplebuddylist, cnode);
}
for (bnode = cnode->child; bnode; bnode = bnode->next) {
klass->update(purplebuddylist, bnode);
}
}
}
void purple_blist_add_group(PurpleGroup *group, PurpleBlistNode *node)
{
PurpleBuddyListClass *klass = NULL;
PurpleBuddyListPrivate *priv = NULL;
PurpleBlistNode *gnode = (PurpleBlistNode*)group;
gchar* key;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
g_return_if_fail(PURPLE_IS_GROUP(group));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
priv = purple_buddy_list_get_instance_private(purplebuddylist);
/* if we're moving to overtop of ourselves, do nothing */
if (gnode == node) {
if (!priv->root) {
node = NULL;
} else {
return;
}
}
if (purple_blist_find_group(purple_group_get_name(group))) {
/* This is just being moved */
if (klass && klass->remove) {
klass->remove(purplebuddylist,
(PurpleBlistNode *)group);
}
if (gnode == priv->root) {
priv->root = gnode->next;
}
if (gnode->prev)
gnode->prev->next = gnode->next;
if (gnode->next)
gnode->next->prev = gnode->prev;
} else {
key = purple_blist_fold_name(purple_group_get_name(group));
g_hash_table_insert(groups_cache, key, group);
}
if (node && PURPLE_IS_GROUP(node)) {
gnode->next = node->next;
gnode->prev = node;
if (node->next)
node->next->prev = gnode;
node->next = gnode;
} else {
if (priv->root) {
priv->root->prev = gnode;
}
gnode->next = priv->root;
gnode->prev = NULL;
priv->root = gnode;
}
if (klass && klass->save_node) {
klass->save_node(purplebuddylist, gnode);
for (node = gnode->child; node; node = node->next) {
klass->save_node(purplebuddylist, node);
}
}
if (klass && klass->update) {
klass->update(purplebuddylist, gnode);
for (node = gnode->child; node; node = node->next) {
klass->update(purplebuddylist, node);
}
}
purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
gnode);
}
void purple_blist_remove_contact(PurpleContact *contact)
{
PurpleBuddyListClass *klass = NULL;
PurpleBlistNode *node, *gnode;
PurpleGroup *group;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
g_return_if_fail(PURPLE_IS_CONTACT(contact));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
node = (PurpleBlistNode *)contact;
gnode = node->parent;
group = PURPLE_GROUP(gnode);
if (node->child) {
/*
* If this contact has children then remove them. When the last
* buddy is removed from the contact, the contact is automatically
* deleted.
*/
while (node->child->next) {
purple_blist_remove_buddy((PurpleBuddy*)node->child);
}
/*
* Remove the last buddy and trigger the deletion of the contact.
* It would probably be cleaner if contact-deletion was done after
* a timeout? Or if it had to be done manually, like below?
*/
purple_blist_remove_buddy((PurpleBuddy*)node->child);
} else {
/* Remove the node from its parent */
if (gnode->child == node)
gnode->child = node->next;
if (node->prev)
node->prev->next = node->next;
if (node->next)
node->next->prev = node->prev;
purple_counting_node_change_total_size(PURPLE_COUNTING_NODE(group), -1);
/* Update the UI */
if (klass && klass->remove) {
klass->remove(purplebuddylist, node);
}
if (klass && klass->remove_node) {
klass->remove_node(purplebuddylist, node);
}
purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
PURPLE_BLIST_NODE(contact));
/* Delete the node */
g_object_unref(contact);
}
}
void purple_blist_remove_buddy(PurpleBuddy *buddy)
{
PurpleBuddyListClass *klass = NULL;
PurpleBuddyListPrivate *priv = NULL;
PurpleBlistNode *node, *cnode, *gnode;
PurpleCountingNode *contact_counter, *group_counter;
PurpleContact *contact;
PurpleGroup *group;
struct _purple_hbuddy hb;
GHashTable *account_buddies;
PurpleAccount *account;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
g_return_if_fail(PURPLE_IS_BUDDY(buddy));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
priv = purple_buddy_list_get_instance_private(purplebuddylist);
account = purple_buddy_get_account(buddy);
node = PURPLE_BLIST_NODE(buddy);
cnode = node->parent;
gnode = (cnode != NULL) ? cnode->parent : NULL;
contact = (PurpleContact *)cnode;
group = (PurpleGroup *)gnode;
/* Remove the node from its parent */
if (node->prev)
node->prev->next = node->next;
if (node->next)
node->next->prev = node->prev;
if ((cnode != NULL) && (cnode->child == node))
cnode->child = node->next;
/* Adjust size counts */
if (contact != NULL) {
contact_counter = PURPLE_COUNTING_NODE(contact);
group_counter = PURPLE_COUNTING_NODE(group);
if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
purple_counting_node_change_online_count(contact_counter, -1);
if (purple_counting_node_get_online_count(contact_counter) == 0)
purple_counting_node_change_online_count(group_counter, -1);
}
if (purple_account_is_connected(account)) {
purple_counting_node_change_current_size(contact_counter, -1);
if (purple_counting_node_get_current_size(contact_counter) == 0)
purple_counting_node_change_current_size(group_counter, -1);
}
purple_counting_node_change_total_size(contact_counter, -1);
/* Re-sort the contact */
if (cnode->child && purple_contact_get_priority_buddy(contact) == buddy) {
purple_contact_invalidate_priority_buddy(contact);
if (klass && klass->update) {
klass->update(purplebuddylist, cnode);
}
}
}
/* Remove this buddy from the buddies hash table */
hb.name = (gchar *)purple_normalize(account, purple_buddy_get_name(buddy));
hb.account = account;
hb.group = gnode;
g_hash_table_remove(priv->buddies, &hb);
account_buddies = g_hash_table_lookup(buddies_cache, account);
g_hash_table_remove(account_buddies, &hb);
/* Update the UI */
if (klass && klass->remove) {
klass->remove(purplebuddylist, node);
}
if (klass && klass->remove_node) {
klass->remove_node(purplebuddylist, node);
}
/* Remove this buddy's pounces */
purple_pounce_destroy_all_by_buddy(buddy);
/* Signal that the buddy has been removed before freeing the memory for it */
purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
PURPLE_BLIST_NODE(buddy));
g_object_unref(buddy);
/* If the contact is empty then remove it */
if ((contact != NULL) && !cnode->child)
purple_blist_remove_contact(contact);
}
void purple_blist_remove_chat(PurpleChat *chat)
{
PurpleBuddyListClass *klass = NULL;
PurpleBlistNode *node, *gnode;
PurpleGroup *group;
PurpleCountingNode *group_counter;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
g_return_if_fail(PURPLE_IS_CHAT(chat));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
node = (PurpleBlistNode *)chat;
gnode = node->parent;
group = (PurpleGroup *)gnode;
if (gnode != NULL)
{
/* Remove the node from its parent */
if (gnode->child == node)
gnode->child = node->next;
if (node->prev)
node->prev->next = node->next;
if (node->next)
node->next->prev = node->prev;
/* Adjust size counts */
group_counter = PURPLE_COUNTING_NODE(group);
if (purple_account_is_connected(purple_chat_get_account(chat))) {
purple_counting_node_change_online_count(group_counter, -1);
purple_counting_node_change_current_size(group_counter, -1);
}
purple_counting_node_change_total_size(group_counter, -1);
}
/* Update the UI */
if (klass && klass->remove) {
klass->remove(purplebuddylist, node);
}
if (klass && klass->remove_node) {
klass->remove_node(purplebuddylist, node);
}
purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
PURPLE_BLIST_NODE(chat));
/* Delete the node */
g_object_unref(chat);
}
void purple_blist_remove_group(PurpleGroup *group)
{
PurpleBuddyListClass *klass = NULL;
PurpleBuddyListPrivate *priv = NULL;
PurpleBlistNode *node;
GList *l;
gchar* key;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
g_return_if_fail(PURPLE_IS_GROUP(group));
if (group == purple_blist_get_default_group())
purple_debug_warning("buddylist", "cannot remove default group");
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
priv = purple_buddy_list_get_instance_private(purplebuddylist);
node = (PurpleBlistNode *)group;
/* Make sure the group is empty */
if (node->child)
return;
/* Remove the node from its parent */
if (priv->root == node) {
priv->root = node->next;
}
if (node->prev)
node->prev->next = node->next;
if (node->next)
node->next->prev = node->prev;
key = purple_blist_fold_name(purple_group_get_name(group));
g_hash_table_remove(groups_cache, key);
g_free(key);
/* Update the UI */
if (klass && klass->remove) {
klass->remove(purplebuddylist, node);
}
if (klass && klass->remove_node) {
klass->remove_node(purplebuddylist, node);
}
purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
PURPLE_BLIST_NODE(group));
/* Remove the group from all accounts that are online */
for (l = purple_connections_get_all(); l != NULL; l = l->next)
{
PurpleConnection *gc = (PurpleConnection *)l->data;
if (purple_connection_get_state(gc) == PURPLE_CONNECTION_CONNECTED)
purple_account_remove_group(purple_connection_get_account(gc), group);
}
/* Delete the node */
g_object_unref(group);
}
PurpleBuddy *purple_blist_find_buddy(PurpleAccount *account, const char *name)
{
PurpleBuddyListPrivate *priv =
purple_buddy_list_get_instance_private(purplebuddylist);
PurpleBuddy *buddy;
struct _purple_hbuddy hb;
PurpleBlistNode *group;
g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
hb.account = account;
hb.name = (gchar *)purple_normalize(account, name);
for (group = priv->root; group; group = group->next) {
if (!group->child)
continue;
hb.group = group;
if ((buddy = g_hash_table_lookup(priv->buddies, &hb))) {
return buddy;
}
}
return NULL;
}
PurpleBuddy *purple_blist_find_buddy_in_group(PurpleAccount *account, const char *name,
PurpleGroup *group)
{
PurpleBuddyListPrivate *priv =
purple_buddy_list_get_instance_private(purplebuddylist);
struct _purple_hbuddy hb;
g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
hb.name = (gchar *)purple_normalize(account, name);
hb.account = account;
hb.group = (PurpleBlistNode*)group;
return g_hash_table_lookup(priv->buddies, &hb);
}
static void find_acct_buddies(gpointer key, gpointer value, gpointer data)
{
PurpleBuddy *buddy = value;
GSList **list = data;
*list = g_slist_prepend(*list, buddy);
}
GSList *purple_blist_find_buddies(PurpleAccount *account, const char *name)
{
PurpleBuddyListPrivate *priv =
purple_buddy_list_get_instance_private(purplebuddylist);
PurpleBuddy *buddy;
PurpleBlistNode *node;
GSList *ret = NULL;
g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
if ((name != NULL) && (*name != '\0')) {
struct _purple_hbuddy hb;
hb.name = (gchar *)purple_normalize(account, name);
hb.account = account;
for (node = priv->root; node != NULL; node = node->next) {
if (!node->child)
continue;
hb.group = node;
if ((buddy = g_hash_table_lookup(priv->buddies,
&hb)) != NULL)
ret = g_slist_prepend(ret, buddy);
}
} else {
GSList *list = NULL;
GHashTable *buddies = g_hash_table_lookup(buddies_cache, account);
g_hash_table_foreach(buddies, find_acct_buddies, &list);
ret = list;
}
return ret;
}
PurpleGroup *purple_blist_find_group(const char *name)
{
gchar* key;
PurpleGroup *group;
g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
if (name == NULL || name[0] == '\0')
name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
if (purple_strequal(name, "Buddies"))
name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
if (purple_strequal(name, localized_default_group_name))
name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
key = purple_blist_fold_name(name);
group = g_hash_table_lookup(groups_cache, key);
g_free(key);
return group;
}
PurpleGroup *
purple_blist_get_default_group(void)
{
PurpleGroup *group;
group = purple_blist_find_group(PURPLE_BLIST_DEFAULT_GROUP_NAME);
if (!group) {
group = purple_group_new(PURPLE_BLIST_DEFAULT_GROUP_NAME);
purple_blist_add_group(group, NULL);
}
return group;
}
PurpleChat *
purple_blist_find_chat(PurpleAccount *account, const char *name)
{
char *chat_name;
PurpleChat *chat;
PurpleProtocol *protocol = NULL;
PurpleProtocolChatEntry *pce;
PurpleBlistNode *node, *group;
GList *parts;
char *normname;
g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
if (!purple_account_is_connected(account))
return NULL;
protocol = purple_protocols_find(purple_account_get_protocol_id(account));
if (PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT, find_blist_chat))
return purple_protocol_client_iface_find_blist_chat(protocol, account, name);
normname = g_strdup(purple_normalize(account, name));
for (group = purple_blist_get_default_root(); group != NULL;
group = group->next) {
for (node = group->child; node != NULL; node = node->next) {
if (PURPLE_IS_CHAT(node)) {
chat = (PurpleChat*)node;
if (account != purple_chat_get_account(chat))
continue;
parts = purple_protocol_chat_iface_info(protocol,
purple_account_get_connection(purple_chat_get_account(chat)));
pce = parts->data;
chat_name = g_hash_table_lookup(purple_chat_get_components(chat),
pce->identifier);
g_list_free_full(parts, g_free);
if (purple_chat_get_account(chat) == account && chat_name != NULL &&
purple_strequal(purple_normalize(account, chat_name), normname)) {
g_free(normname);
return chat;
}
}
}
}
g_free(normname);
return NULL;
}
void purple_blist_add_account(PurpleAccount *account)
{
PurpleBuddyListClass *klass = NULL;
PurpleBlistNode *gnode, *cnode, *bnode;
PurpleCountingNode *contact_counter, *group_counter;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
if (!klass || !klass->update) {
return;
}
for (gnode = purple_blist_get_default_root(); gnode;
gnode = gnode->next) {
if (!PURPLE_IS_GROUP(gnode))
continue;
for (cnode = gnode->child; cnode; cnode = cnode->next) {
if (PURPLE_IS_CONTACT(cnode)) {
gboolean recompute = FALSE;
for (bnode = cnode->child; bnode; bnode = bnode->next) {
if (PURPLE_IS_BUDDY(bnode) &&
purple_buddy_get_account(PURPLE_BUDDY(bnode)) == account) {
recompute = TRUE;
contact_counter = PURPLE_COUNTING_NODE(cnode);
group_counter = PURPLE_COUNTING_NODE(gnode);
purple_counting_node_change_current_size(contact_counter, +1);
if (purple_counting_node_get_current_size(contact_counter) == 1)
purple_counting_node_change_current_size(group_counter, +1);
klass->update(
purplebuddylist,
bnode);
}
}
if (recompute ||
purple_blist_node_get_bool(
cnode, "show_offline")) {
purple_contact_invalidate_priority_buddy(
(PurpleContact *)cnode);
klass->update(purplebuddylist,
cnode);
}
} else if (PURPLE_IS_CHAT(cnode) &&
purple_chat_get_account(PURPLE_CHAT(cnode)) == account) {
group_counter = PURPLE_COUNTING_NODE(gnode);
purple_counting_node_change_online_count(group_counter, +1);
purple_counting_node_change_current_size(group_counter, +1);
klass->update(purplebuddylist, cnode);
}
}
klass->update(purplebuddylist, gnode);
}
}
void purple_blist_remove_account(PurpleAccount *account)
{
PurpleBuddyListClass *klass = NULL;
PurpleBlistNode *gnode, *cnode, *bnode;
PurpleCountingNode *contact_counter, *group_counter;
PurpleBuddy *buddy;
PurpleChat *chat;
PurpleContact *contact;
PurpleGroup *group;
GList *list = NULL, *iter = NULL;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
for (gnode = purple_blist_get_default_root(); gnode;
gnode = gnode->next) {
if (!PURPLE_IS_GROUP(gnode))
continue;
group = (PurpleGroup *)gnode;
for (cnode = gnode->child; cnode; cnode = cnode->next) {
if (PURPLE_IS_CONTACT(cnode)) {
gboolean recompute = FALSE;
contact = (PurpleContact *)cnode;
for (bnode = cnode->child; bnode; bnode = bnode->next) {
if (!PURPLE_IS_BUDDY(bnode))
continue;
buddy = (PurpleBuddy *)bnode;
if (account == purple_buddy_get_account(buddy)) {
PurplePresence *presence;
presence = purple_buddy_get_presence(buddy);
contact_counter = PURPLE_COUNTING_NODE(contact);
group_counter = PURPLE_COUNTING_NODE(group);
if(purple_presence_is_online(presence)) {
purple_counting_node_change_online_count(contact_counter, -1);
if (purple_counting_node_get_online_count(contact_counter) == 0)
purple_counting_node_change_online_count(group_counter, -1);
purple_blist_node_set_int(PURPLE_BLIST_NODE(buddy),
"last_seen", time(NULL));
}
purple_counting_node_change_current_size(contact_counter, -1);
if (purple_counting_node_get_current_size(contact_counter) == 0)
purple_counting_node_change_current_size(group_counter, -1);
if (!g_list_find(list, presence))
list = g_list_prepend(list, presence);
if (purple_contact_get_priority_buddy(contact) == buddy)
purple_contact_invalidate_priority_buddy(contact);
else
recompute = TRUE;
if (klass && klass->remove) {
klass->remove(
purplebuddylist,
bnode);
}
}
}
if (recompute) {
purple_contact_invalidate_priority_buddy(contact);
if (klass && klass->update) {
klass->update(purplebuddylist,
cnode);
}
}
} else if (PURPLE_IS_CHAT(cnode)) {
chat = PURPLE_CHAT(cnode);
if(purple_chat_get_account(chat) == account) {
group_counter = PURPLE_COUNTING_NODE(group);
purple_counting_node_change_current_size(group_counter, -1);
purple_counting_node_change_online_count(group_counter, -1);
if (klass && klass->remove) {
klass->remove(purplebuddylist,
cnode);
}
}
}
}
}
for (iter = list; iter; iter = iter->next)
{
purple_presence_set_status_active(iter->data, "offline", TRUE);
}
g_list_free(list);
}
void
purple_blist_walk(PurpleBlistWalkFunc group_func,
PurpleBlistWalkFunc chat_func,
PurpleBlistWalkFunc meta_contact_func,
PurpleBlistWalkFunc contact_func,
gpointer data)
{
PurpleBlistNode *group = NULL, *meta_contact = NULL, *contact = NULL;
for (group = purple_blist_get_default_root(); group != NULL;
group = group->next) {
if(group_func != NULL) {
group_func(group, data);
}
for(meta_contact = group->child; meta_contact != NULL; meta_contact = meta_contact->next) {
if(PURPLE_IS_CONTACT(meta_contact)) {
if(meta_contact_func != NULL) {
meta_contact_func(meta_contact, data);
}
if(contact_func != NULL) {
for(contact = meta_contact->child; contact != NULL; contact = contact->next) {
contact_func(contact, data);
}
}
} else {
if(PURPLE_IS_CHAT(meta_contact) && chat_func != NULL) {
chat_func(meta_contact, data);
}
}
}
}
}
void
purple_blist_request_add_buddy(PurpleAccount *account, const char *username,
const char *group, const char *alias)
{
PurpleBuddyListClass *klass = NULL;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
if (klass != NULL && klass->request_add_buddy != NULL) {
klass->request_add_buddy(purplebuddylist, account, username,
group, alias);
}
}
void
purple_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group,
const char *alias, const char *name)
{
PurpleBuddyListClass *klass = NULL;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
if (klass != NULL && klass->request_add_chat != NULL) {
klass->request_add_chat(purplebuddylist, account, group, alias,
name);
}
}
void
purple_blist_request_add_group(void)
{
PurpleBuddyListClass *klass = NULL;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
if (klass != NULL && klass->request_add_group != NULL) {
klass->request_add_group(purplebuddylist);
}
}
void
purple_blist_new_node(PurpleBuddyList *list, PurpleBlistNode *node)
{
PurpleBuddyListClass *klass = NULL;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
if (klass && klass->new_node) {
klass->new_node(list, node);
}
}
void
purple_blist_update_node(PurpleBuddyList *list, PurpleBlistNode *node)
{
PurpleBuddyListClass *klass = NULL;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
if (klass && klass->update) {
klass->update(list, node);
}
}
void
purple_blist_save_node(PurpleBuddyList *list, PurpleBlistNode *node)
{
PurpleBuddyListClass *klass = NULL;
g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
if (klass && klass->save_node) {
klass->save_node(list, node);
}
}
void
purple_blist_save_account(PurpleBuddyList *list, PurpleAccount *account)
{
PurpleBuddyListClass *klass = NULL;
/* XXX: There's a chicken and egg problem with the accounts api, where
* it'll call this function before purple_blist_init is called, this will
* cause the following g_return_if_fail to fail, and muck up the logs. We
* need to find a better fix for this, but this gets rid of it for now.
*/
if(G_UNLIKELY(list == NULL && purplebuddylist == NULL)) {
return;
}
g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
if (klass && klass->save_account) {
klass->save_account(list, account);
}
}
const gchar *
_purple_blist_get_localized_default_group_name(void)
{
return localized_default_group_name;
}
void *
purple_blist_get_handle(void)
{
static int handle;
return &handle;
}
void
purple_blist_init(void)
{
void *handle = purple_blist_get_handle();
/* Set a default, which can't be done as a static initializer. */
buddy_list_type = PURPLE_TYPE_BUDDY_LIST;
purple_signal_register(handle, "buddy-status-changed",
purple_marshal_VOID__POINTER_POINTER_POINTER,
G_TYPE_NONE, 3, PURPLE_TYPE_BUDDY, PURPLE_TYPE_STATUS,
PURPLE_TYPE_STATUS);
purple_signal_register(handle, "buddy-privacy-changed",
purple_marshal_VOID__POINTER, G_TYPE_NONE,
1, PURPLE_TYPE_BUDDY);
purple_signal_register(handle, "buddy-idle-changed",
purple_marshal_VOID__POINTER_INT_INT, G_TYPE_NONE,
3, PURPLE_TYPE_BUDDY, G_TYPE_INT, G_TYPE_INT);
purple_signal_register(handle, "buddy-signed-on",
purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
PURPLE_TYPE_BUDDY);
purple_signal_register(handle, "buddy-signed-off",
purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
PURPLE_TYPE_BUDDY);
purple_signal_register(handle, "buddy-got-login-time",
purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
PURPLE_TYPE_BUDDY);
purple_signal_register(handle, "blist-node-added",
purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
PURPLE_TYPE_BLIST_NODE);
purple_signal_register(handle, "blist-node-removed",
purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
PURPLE_TYPE_BLIST_NODE);
purple_signal_register(handle, "buddy-removed-from-group",
purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
PURPLE_TYPE_BUDDY);
purple_signal_register(handle, "buddy-icon-changed",
purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
PURPLE_TYPE_BUDDY);
purple_signal_register(handle, "update-idle", purple_marshal_VOID,
G_TYPE_NONE, 0);
purple_signal_register(handle, "blist-node-extended-menu",
purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
PURPLE_TYPE_BLIST_NODE,
G_TYPE_POINTER); /* (GList **) */
purple_signal_register(handle, "blist-node-aliased",
purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
PURPLE_TYPE_BLIST_NODE, G_TYPE_STRING);
purple_signal_register(handle, "buddy-caps-changed",
purple_marshal_VOID__POINTER_INT_INT, G_TYPE_NONE,
3, PURPLE_TYPE_BUDDY, G_TYPE_INT, G_TYPE_INT);
purple_signal_connect(purple_accounts_get_handle(), "account-created",
handle,
PURPLE_CALLBACK(purple_blist_buddies_cache_add_account),
NULL);
purple_signal_connect(purple_accounts_get_handle(), "account-destroying",
handle,
PURPLE_CALLBACK(purple_blist_buddies_cache_remove_account),
NULL);
}
static void
blist_node_destroy(PurpleBuddyListClass *klass, PurpleBuddyList *list,
PurpleBlistNode *node)
{
PurpleBlistNode *child, *next_child;
child = node->child;
while (child) {
next_child = child->next;
blist_node_destroy(klass, list, child);
child = next_child;
}
/* Allow the UI to free data */
node->parent = NULL;
node->child = NULL;
node->next = NULL;
node->prev = NULL;
if (klass && klass->remove) {
klass->remove(list, node);
}
g_object_unref(node);
}
void
purple_blist_uninit(void)
{
/* This happens if we quit before purple_set_blist is called. */
if (purplebuddylist == NULL)
return;
if (save_timer != 0) {
g_source_remove(save_timer);
save_timer = 0;
purple_blist_sync();
}
purple_debug(PURPLE_DEBUG_INFO, "buddylist", "Destroying\n");
g_hash_table_destroy(buddies_cache);
g_hash_table_destroy(groups_cache);
buddies_cache = NULL;
groups_cache = NULL;
g_clear_object(&purplebuddylist);
g_free(localized_default_group_name);
localized_default_group_name = NULL;
purple_signals_disconnect_by_handle(purple_blist_get_handle());
purple_signals_unregister_by_instance(purple_blist_get_handle());
}
/**************************************************************************
* GObject code
**************************************************************************/
/* GObject initialization function */
static void
purple_buddy_list_init(PurpleBuddyList *blist)
{
PurpleBuddyListPrivate *priv =
purple_buddy_list_get_instance_private(blist);
priv->buddies = g_hash_table_new_full(
(GHashFunc)_purple_blist_hbuddy_hash,
(GEqualFunc)_purple_blist_hbuddy_equal,
(GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
}
/* GObject finalize function */
static void
purple_buddy_list_finalize(GObject *object)
{
PurpleBuddyList *list = PURPLE_BUDDY_LIST(object);
PurpleBuddyListClass *klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
PurpleBuddyListPrivate *priv =
purple_buddy_list_get_instance_private(list);
PurpleBlistNode *node, *next_node;
g_hash_table_destroy(priv->buddies);
node = priv->root;
while (node) {
next_node = node->next;
blist_node_destroy(klass, list, node);
node = next_node;
}
priv->root = NULL;
G_OBJECT_CLASS(purple_buddy_list_parent_class)->finalize(object);
}
/* Class initializer function */
static void purple_buddy_list_class_init(PurpleBuddyListClass *klass)
{
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
obj_class->finalize = purple_buddy_list_finalize;
klass->save_node = purple_blist_real_save_node;
klass->remove_node = purple_blist_real_save_node;
klass->save_account = purple_blist_real_save_account;
}