Mercurial > ~mikael > mcabber > hg
diff mcabber/mcabber/roster.c @ 1668:41c26b7d2890
Install mcabber headers
* Change mcabber headers naming scheme
* Move 'src/' -> 'mcabber/'
* Add missing include <mcabber/config.h>'s
* Create and install clean config.h version in 'include/'
* Move "dirty" config.h version to 'mcabber/'
* Add $(top_srcdir) to compiler include path
* Update modules HOWTO
author | Myhailo Danylenko <isbear@ukrpost.net> |
---|---|
date | Mon, 18 Jan 2010 15:36:19 +0200 |
parents | mcabber/src/roster.c@c5ee395fbc8c |
children | b2e0083891cc |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/roster.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,1624 @@ +/* + * roster.c -- Local roster implementation + * + * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net> + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <string.h> + +#include "roster.h" +#include "utils.h" +#include "hooks.h" + +extern void hlog_save_state(void); + +char *strrole[] = { /* Should match enum in roster.h */ + "none", + "moderator", + "participant", + "visitor" +}; + +char *straffil[] = { /* Should match enum in roster.h */ + "none", + "owner", + "admin", + "member", + "outcast" +}; + +char *strprintstatus[] = { /* Should match enum in roster.h */ + "default", + "none", + "in_and_out", + "all" +}; + +char *strautowhois[] = { /* Should match enum in roster.h */ + "default", + "off", + "on", +}; + +/* Resource structure */ + +typedef struct { + gchar *name; + gchar prio; + enum imstatus status; + gchar *status_msg; + time_t status_timestamp; + enum imrole role; + enum imaffiliation affil; + gchar *realjid; /* for chatrooms, if buddy's real jid is known */ + guint events; + char *caps; +#ifdef JEP0022 + struct jep0022 jep22; +#endif +#ifdef JEP0085 + struct jep0085 jep85; +#endif +#ifdef HAVE_GPGME + struct pgp_data pgpdata; +#endif +} res; + +/* This is a private structure type for the roster */ + +typedef struct { + gchar *name; + gchar *jid; + guint type; + enum subscr subscription; + GSList *resource; + + /* For groupchats */ + gchar *nickname; + gchar *topic; + guint inside_room; + guint print_status; + guint auto_whois; + + /* on_server is TRUE if the item is present on the server roster */ + guint on_server; + + /* To keep track of last status message */ + gchar *offline_status_message; + + /* Flag used for the UI */ + guint flags; + + // list: user -> points to his group; group -> points to its users list + GSList *list; +} roster; + + +/* ### Variables ### */ + +static guchar display_filter; +static GSList *groups; +static GSList *unread_list; +static GHashTable *unread_jids; +GList *buddylist; +GList *current_buddy; +GList *alternate_buddy; + +static roster roster_special; + +static int unread_jid_del(const char *jid); + +#define DFILTER_ALL 63 +#define DFILTER_ONLINE 62 + + +/* ### Initialization ### */ + +void roster_init(void) +{ + roster_special.name = SPECIAL_BUFFER_STATUS_ID; + roster_special.type = ROSTER_TYPE_SPECIAL; +} + +/* ### Resources functions ### */ + +static inline void free_resource_data(res *p_res) +{ + if (!p_res) + return; + g_free((gchar*)p_res->status_msg); + g_free((gchar*)p_res->name); + g_free((gchar*)p_res->realjid); +#ifdef JEP0022 + g_free(p_res->jep22.last_msgid_sent); + g_free(p_res->jep22.last_msgid_rcvd); +#endif +#ifdef HAVE_GPGME + g_free(p_res->pgpdata.sign_keyid); +#endif + g_free(p_res->caps); + g_free(p_res); +} + +static void free_all_resources(GSList **reslist) +{ + GSList *lip; + + for (lip = *reslist; lip ; lip = g_slist_next(lip)) + free_resource_data((res*)lip->data); + // Free all nodes but the first (which is static) + g_slist_free(*reslist); + *reslist = NULL; +} + +// Resources are sorted in ascending order +static gint resource_compare_prio(res *a, res *b) { + //return (a->prio - b->prio); + if (a->prio < b->prio) return -1; + else return 1; +} + +// get_resource(rost, resname) +// Return a pointer to the resource with name resname, in rost's resources list +// - if rost has no resources, return NULL +// - if resname is defined, return the match or NULL +// - if resname is NULL, the last resource is returned, currently +// This could change in the future, because we should return the best one +// (priority? last used? and fall back to the first resource) +// +static res *get_resource(roster *rost, const char *resname) +{ + GSList *p; + res *r = NULL; + + for (p = rost->resource; p; p = g_slist_next(p)) { + r = p->data; + if (resname && !strcmp(r->name, resname)) + return r; + } + + // The last resource is one of the resources with the highest priority, + // however, we don't know if it is the more-recently-used. + if (!resname) return r; + return NULL; +} + +// get_or_add_resource(rost, resname, priority) +// - if there is a "resname" resource in rost's resources, return a pointer +// on this resource +// - if not, add the resource, set the name, and return a pointer on this +// new resource +static res *get_or_add_resource(roster *rost, const char *resname, gchar prio) +{ + GSList *p; + res *nres; + + if (!resname) return NULL; + + for (p = rost->resource; p; p = g_slist_next(p)) { + res *r = p->data; + if (!strcmp(r->name, resname)) { + if (prio != r->prio) { + r->prio = prio; + rost->resource = g_slist_sort(rost->resource, + (GCompareFunc)&resource_compare_prio); + } + return r; + } + } + + // Resource not found + nres = g_new0(res, 1); + nres->name = g_strdup(resname); + nres->prio = prio; + rost->resource = g_slist_insert_sorted(rost->resource, nres, + (GCompareFunc)&resource_compare_prio); + return nres; +} + +static void del_resource(roster *rost, const char *resname) +{ + GSList *p; + GSList *p_res_elt = NULL; + res *p_res; + + if (!resname) return; + + for (p = rost->resource; p; p = g_slist_next(p)) { + res *r = p->data; + if (!strcmp(r->name, resname)) + p_res_elt = p; + } + + if (!p_res_elt) return; // Resource not found + + p_res = p_res_elt->data; + + // Keep a copy of the status message when a buddy goes offline + if (g_slist_length(rost->resource) == 1) { + g_free(rost->offline_status_message); + rost->offline_status_message = p_res->status_msg; + p_res->status_msg = NULL; + } + + // Free allocations and delete resource node + free_resource_data(p_res); + rost->resource = g_slist_delete_link(rost->resource, p_res_elt); + return; +} + + +/* ### Roster functions ### */ + +static inline void free_roster_user_data(roster *roster_usr) +{ + if (!roster_usr) + return; + g_free((gchar*)roster_usr->jid); + g_free((gchar*)roster_usr->name); + g_free((gchar*)roster_usr->nickname); + g_free((gchar*)roster_usr->topic); + g_free((gchar*)roster_usr->offline_status_message); + free_all_resources(&roster_usr->resource); + g_free(roster_usr); +} + +// Comparison function used to search in the roster (compares jids and types) +static gint roster_compare_jid_type(roster *a, roster *b) { + if (! (a->type & b->type)) + return -1; // arbitrary (but should be != 0, of course) + return strcasecmp(a->jid, b->jid); +} + +// Comparison function used to search in the roster (compares names and types) +static gint roster_compare_name_type(roster *a, roster *b) { + if (! (a->type & b->type)) + return -1; // arbitrary (but should be != 0, of course) + return strcmp(a->name, b->name); +} + +// Comparison function used to sort the roster (by name) +static gint roster_compare_name(roster *a, roster *b) { + return strcmp(a->name, b->name); +} + +// Finds a roster element (user, group, agent...), by jid or name +// If roster_type is 0, returns match of any type. +// Returns the roster GSList element, or NULL if jid/name not found +GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type) +{ + GSList *sl_roster_elt = groups; + GSList *resource; + roster sample; + GCompareFunc comp; + + if (!jidname) return NULL; + + if (!roster_type) + roster_type = ROSTER_TYPE_USER | ROSTER_TYPE_ROOM | + ROSTER_TYPE_AGENT | ROSTER_TYPE_GROUP; + + sample.type = roster_type; + if (type == jidsearch) { + sample.jid = (gchar*)jidname; + comp = (GCompareFunc)&roster_compare_jid_type; + } else if (type == namesearch) { + sample.name = (gchar*)jidname; + comp = (GCompareFunc)&roster_compare_name_type; + } else + return NULL; // Should not happen... + + while (sl_roster_elt) { + roster *roster_elt = (roster*)sl_roster_elt->data; + if (roster_type & ROSTER_TYPE_GROUP) { + if ((type == namesearch) && !strcmp(jidname, roster_elt->name)) + return sl_roster_elt; + } + resource = g_slist_find_custom(roster_elt->list, &sample, comp); + if (resource) return resource; + sl_roster_elt = g_slist_next(sl_roster_elt); + } + return NULL; +} + +// Returns pointer to new group, or existing group with that name +GSList *roster_add_group(const char *name) +{ + roster *roster_grp; + GSList *p_group; + + // #1 Check name doesn't already exist + p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP); + if (!p_group) { + // #2 Create the group node + roster_grp = g_new0(roster, 1); + roster_grp->name = g_strdup(name); + roster_grp->type = ROSTER_TYPE_GROUP; + // #3 Insert (sorted) + groups = g_slist_insert_sorted(groups, roster_grp, + (GCompareFunc)&roster_compare_name); + p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP); + } + return p_group; +} + +// Returns a pointer to the new user, or existing user with that name +// Note: if onserver is -1, the flag won't be changed. +GSList *roster_add_user(const char *jid, const char *name, const char *group, + guint type, enum subscr esub, gint onserver) +{ + roster *roster_usr; + roster *my_group; + GSList *slist; + + if ((type != ROSTER_TYPE_USER) && + (type != ROSTER_TYPE_ROOM) && + (type != ROSTER_TYPE_AGENT)) { + // XXX Error message? + return NULL; + } + + // Let's be arbitrary: default group has an empty name (""). + if (!group) group = ""; + + // #1 Check this user doesn't already exist + slist = roster_find(jid, jidsearch, 0); + if (slist) { + char *oldgroupname; + // That's an update + roster_usr = slist->data; + roster_usr->subscription = esub; + if (onserver >= 0) + buddy_setonserverflag(slist->data, onserver); + if (name) + buddy_setname(slist->data, (char*)name); + // Let's check if the group name has changed + oldgroupname = ((roster*)((GSList*)roster_usr->list)->data)->name; + if (group && strcmp(oldgroupname, group)) { + buddy_setgroup(slist->data, (char*)group); + // Note: buddy_setgroup() updates the user lists so we cannot + // use slist anymore. + return roster_find(jid, jidsearch, 0); + } + return slist; + } + // #2 add group if necessary + slist = roster_add_group(group); + if (!slist) return NULL; + my_group = (roster*)slist->data; + // #3 Create user node + roster_usr = g_new0(roster, 1); + roster_usr->jid = g_strdup(jid); + if (name) { + roster_usr->name = g_strdup(name); + } else { + gchar *p, *str = g_strdup(jid); + p = strchr(str, JID_RESOURCE_SEPARATOR); + if (p) *p = '\0'; + roster_usr->name = g_strdup(str); + g_free(str); + } + if (unread_jid_del(jid)) { + roster_usr->flags |= ROSTER_FLAG_MSG; + // Append the roster_usr to unread_list + unread_list = g_slist_append(unread_list, roster_usr); + } + roster_usr->type = type; + roster_usr->subscription = esub; + roster_usr->list = slist; // (my_group SList element) + if (onserver == 1) + roster_usr->on_server = TRUE; + // #4 Insert node (sorted) + my_group->list = g_slist_insert_sorted(my_group->list, roster_usr, + (GCompareFunc)&roster_compare_name); + return roster_find(jid, jidsearch, type); +} + +// Removes user (jid) from roster, frees allocated memory +void roster_del_user(const char *jid) +{ + GSList *sl_user, *sl_group; + GSList **sl_group_listptr; + roster *roster_usr; + GSList *node; + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM); + if (sl_user == NULL) + return; + roster_usr = (roster*)sl_user->data; + + // Remove (if present) from unread messages list + node = g_slist_find(unread_list, roster_usr); + if (node) unread_list = g_slist_delete_link(unread_list, node); + // If there is a pending unread message, keep track of it + if (roster_usr->flags & ROSTER_FLAG_MSG) + unread_jid_add(roster_usr->jid); + + sl_group = roster_usr->list; + + // Let's free roster_usr memory (jid, name, status message...) + free_roster_user_data(roster_usr); + + // That's a little complex, we need to dereference twice + sl_group_listptr = &((roster*)(sl_group->data))->list; + *sl_group_listptr = g_slist_delete_link(*sl_group_listptr, sl_user); + + // We need to rebuild the list + if (current_buddy) + buddylist_build(); + // TODO What we could do, too, is to check if the deleted node is + // current_buddy, in which case we could move current_buddy to the + // previous (or next) node. +} + +// Free all roster data and call buddylist_build() to free the buddylist. +void roster_free(void) +{ + GSList *sl_grp = groups; + + // Free unread_list + if (unread_list) { + g_slist_free(unread_list); + unread_list = NULL; + } + + // Walk through groups + while (sl_grp) { + roster *roster_grp = (roster*)sl_grp->data; + GSList *sl_usr = roster_grp->list; + // Walk through this group users + while (sl_usr) { + roster *roster_usr = (roster*)sl_usr->data; + // If there is a pending unread message, keep track of it + if (roster_usr->flags & ROSTER_FLAG_MSG) + unread_jid_add(roster_usr->jid); + // Free roster_usr data (jid, name, status message...) + free_roster_user_data(roster_usr); + sl_usr = g_slist_next(sl_usr); + } + // Free group's users list + if (roster_grp->list) + g_slist_free(roster_grp->list); + // Free group's name and jid + g_free((gchar*)roster_grp->jid); + g_free((gchar*)roster_grp->name); + g_free(roster_grp); + sl_grp = g_slist_next(sl_grp); + } + // Free groups list + if (groups) { + g_slist_free(groups); + groups = NULL; + // Update (i.e. free) buddylist + if (buddylist) + buddylist_build(); + } +} + +// roster_setstatus() +// Note: resname, role, affil and realjid are for room members only +void roster_setstatus(const char *jid, const char *resname, gchar prio, + enum imstatus bstat, const char *status_msg, + time_t status_time, + enum imrole role, enum imaffiliation affil, + const char *realjid) +{ + GSList *sl_user; + roster *roster_usr; + res *p_res; + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); + // If we can't find it, we add it + if (sl_user == NULL) + sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER, + sub_none, -1); + + // If there is no resource name, we can leave now + if (!resname) return; + + roster_usr = (roster*)sl_user->data; + + // New or updated resource + p_res = get_or_add_resource(roster_usr, resname, prio); + p_res->status = bstat; + if (p_res->status_msg) { + g_free((gchar*)p_res->status_msg); + p_res->status_msg = NULL; + } + if (status_msg) + p_res->status_msg = g_strdup(status_msg); + if (!status_time) + time(&status_time); + p_res->status_timestamp = status_time; + + p_res->role = role; + p_res->affil = affil; + + if (p_res->realjid) { + g_free((gchar*)p_res->realjid); + p_res->realjid = NULL; + } + if (realjid) + p_res->realjid = g_strdup(realjid); + + // If bstat is offline, we MUST delete the resource, actually + if (bstat == offline) { + del_resource(roster_usr, resname); + return; + } +} + +// roster_setflags() +// Set one or several flags to value (TRUE/FALSE) +void roster_setflags(const char *jid, guint flags, guint value) +{ + GSList *sl_user; + roster *roster_usr; + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return; + + roster_usr = (roster*)sl_user->data; + if (value) + roster_usr->flags |= flags; + else + roster_usr->flags &= ~flags; +} + +// roster_msg_setflag() +// Set the ROSTER_FLAG_MSG to the given value for the given jid. +// It will update the buddy's group message flag. +// Update the unread messages list too. +void roster_msg_setflag(const char *jid, guint special, guint value) +{ + GSList *sl_user; + roster *roster_usr, *roster_grp; + int new_roster_item = FALSE; + guint unread_list_modified = FALSE; + + if (special) { + //sl_user = roster_find(jid, namesearch, ROSTER_TYPE_SPECIAL); + //if (!sl_user) return; + //roster_usr = (roster*)sl_user->data; + roster_usr = &roster_special; + if (value) { + if (!(roster_usr->flags & ROSTER_FLAG_MSG)) + unread_list_modified = TRUE; + roster_usr->flags |= ROSTER_FLAG_MSG; + // Append the roster_usr to unread_list, but avoid duplicates + if (!g_slist_find(unread_list, roster_usr)) + unread_list = g_slist_append(unread_list, roster_usr); + } else { + if (roster_usr->flags & ROSTER_FLAG_MSG) + unread_list_modified = TRUE; + roster_usr->flags &= ~ROSTER_FLAG_MSG; + if (unread_list) { + GSList *node = g_slist_find(unread_list, roster_usr); + if (node) + unread_list = g_slist_delete_link(unread_list, node); + } + } + goto roster_msg_setflag_return; + } + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); + // If we can't find it, we add it + if (sl_user == NULL) { + sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER, sub_none, -1); + new_roster_item = TRUE; + } + + roster_usr = (roster*)sl_user->data; + roster_grp = (roster*)roster_usr->list->data; + if (value) { + if (!(roster_usr->flags & ROSTER_FLAG_MSG)) + unread_list_modified = TRUE; + // Message flag is TRUE. This is easy, we just have to set both flags + // to TRUE... + roster_usr->flags |= ROSTER_FLAG_MSG; + roster_grp->flags |= ROSTER_FLAG_MSG; // group + // Append the roster_usr to unread_list, but avoid duplicates + if (!g_slist_find(unread_list, roster_usr)) + unread_list = g_slist_append(unread_list, roster_usr); + } else { + // Message flag is FALSE. + guint msg = FALSE; + if (roster_usr->flags & ROSTER_FLAG_MSG) + unread_list_modified = TRUE; + roster_usr->flags &= ~ROSTER_FLAG_MSG; + if (unread_list) { + GSList *node = g_slist_find(unread_list, roster_usr); + if (node) + unread_list = g_slist_delete_link(unread_list, node); + } + // For the group value we need to watch all buddies in this group; + // if one is flagged, then the group will be flagged. + // I will re-use sl_user and roster_usr here, as they aren't used + // anymore. + sl_user = roster_grp->list; + while (sl_user) { + roster_usr = (roster*)sl_user->data; + if (roster_usr->flags & ROSTER_FLAG_MSG) { + msg = TRUE; + break; + } + sl_user = g_slist_next(sl_user); + } + if (!msg) + roster_grp->flags &= ~ROSTER_FLAG_MSG; + else + roster_grp->flags |= ROSTER_FLAG_MSG; + // Actually the "else" part is useless, because the group + // ROSTER_FLAG_MSG should already be set... + } + + if (buddylist && (new_roster_item || !g_list_find(buddylist, roster_usr))) + buddylist_build(); + +roster_msg_setflag_return: + if (unread_list_modified) { + guint unread_count = g_slist_length(unread_list); + hlog_save_state(); + /* Call external command */ + hk_ext_cmd("", 'U', (guchar)MIN(255, unread_count), NULL); + } +} + +const char *roster_getname(const char *jid) +{ + GSList *sl_user; + roster *roster_usr; + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return NULL; // Not in the roster... + + roster_usr = (roster*)sl_user->data; + return roster_usr->name; +} + +const char *roster_getnickname(const char *jid) +{ + GSList *sl_user; + roster *roster_usr; + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return NULL; // Not in the roster... + + roster_usr = (roster*)sl_user->data; + return roster_usr->nickname; +} + +void roster_settype(const char *jid, guint type) +{ + GSList *sl_user; + roster *roster_usr; + + if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL) + return; + + roster_usr = (roster*)sl_user->data; + roster_usr->type = type; +} + +enum imstatus roster_getstatus(const char *jid, const char *resname) +{ + GSList *sl_user; + roster *roster_usr; + res *p_res; + + sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return offline; // Not in the roster, anyway... + + roster_usr = (roster*)sl_user->data; + p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->status; + return offline; +} + +const char *roster_getstatusmsg(const char *jid, const char *resname) +{ + GSList *sl_user; + roster *roster_usr; + res *p_res; + + sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return NULL; // Not in the roster, anyway... + + roster_usr = (roster*)sl_user->data; + p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->status_msg; + return roster_usr->offline_status_message; +} + +guint roster_gettype(const char *jid) +{ + GSList *sl_user; + roster *roster_usr; + + if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL) + return 0; + + roster_usr = (roster*)sl_user->data; + return roster_usr->type; +} + +guint roster_getsubscription(const char *jid) +{ + GSList *sl_user; + roster *roster_usr; + + if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL) + return 0; + + roster_usr = (roster*)sl_user->data; + return roster_usr->subscription; +} + +// roster_unsubscribed() +// We have lost buddy's presence updates; this function clears the status +// message, sets the buddy offline and frees the resources +void roster_unsubscribed(const char *jid) +{ + GSList *sl_user; + roster *roster_usr; + + sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return; + + roster_usr = (roster*)sl_user->data; + free_all_resources(&roster_usr->resource); +} + + +/* ### BuddyList functions ### */ + +// buddylist_set_hide_offline_buddies(hide) +// "hide" values: 1=hide 0=show_all -1=invert +void buddylist_set_hide_offline_buddies(int hide) +{ + if (hide < 0) { // NEG (invert) + if (display_filter == DFILTER_ALL) + display_filter = DFILTER_ONLINE; + else + display_filter = DFILTER_ALL; + } else if (hide == 0) { // FALSE (don't hide -- andfo_) + display_filter = DFILTER_ALL; + } else { // TRUE (hide -- andfo) + display_filter = DFILTER_ONLINE; + } +} + +int buddylist_isset_filter(void) +{ + return (display_filter != DFILTER_ALL); +} + +int buddylist_is_status_filtered(enum imstatus status) +{ + return display_filter & (1 << status); +} + +void buddylist_set_filter(guchar filter) +{ + display_filter = filter; +} + +guchar buddylist_get_filter(void) +{ + return display_filter; +} + +// buddylist_build() +// Creates the buddylist from the roster entries. +void buddylist_build(void) +{ + GSList *sl_roster_elt = groups; + roster *roster_elt; + roster *roster_current_buddy = NULL; + roster *roster_alternate_buddy = NULL; + int shrunk_group; + + // We need to remember which buddy is selected. + if (current_buddy) + roster_current_buddy = BUDDATA(current_buddy); + current_buddy = NULL; + if (alternate_buddy) + roster_alternate_buddy = BUDDATA(alternate_buddy); + alternate_buddy = NULL; + + // Destroy old buddylist + if (buddylist) { + g_list_free(buddylist); + buddylist = NULL; + } + + buddylist = g_list_append(buddylist, &roster_special); + + // Create the new list + while (sl_roster_elt) { + GSList *sl_roster_usrelt; + roster *roster_usrelt; + guint pending_group = TRUE; + roster_elt = (roster*) sl_roster_elt->data; + + shrunk_group = roster_elt->flags & ROSTER_FLAG_HIDE; + + sl_roster_usrelt = roster_elt->list; + while (sl_roster_usrelt) { + roster_usrelt = (roster*) sl_roster_usrelt->data; + + // Buddy will be added if either: + // - buddy's status matches the display_filter + // - buddy has a lock (for example the buddy window is currently open) + // - buddy has a pending (non-read) message + // - group isn't hidden (shrunk) + // - this is the current_buddy + if (roster_usrelt == roster_current_buddy || + buddylist_is_status_filtered(buddy_getstatus((gpointer)roster_usrelt, + NULL)) || + (buddy_getflags((gpointer)roster_usrelt) & + (ROSTER_FLAG_LOCK | ROSTER_FLAG_USRLOCK | ROSTER_FLAG_MSG))) { + // This user should be added. Maybe the group hasn't been added yet? + if (pending_group) { + // It hasn't been done yet + buddylist = g_list_append(buddylist, roster_elt); + pending_group = FALSE; + } + // Add user + // XXX Should we add the user if there is a message and + // the group is shrunk? If so, we'd need to check LOCK flag too, + // perhaps... + if (!shrunk_group) + buddylist = g_list_append(buddylist, roster_usrelt); + } + + sl_roster_usrelt = g_slist_next(sl_roster_usrelt); + } + sl_roster_elt = g_slist_next(sl_roster_elt); + } + + // Check if we can find our saved current_buddy... + if (roster_current_buddy) + current_buddy = g_list_find(buddylist, roster_current_buddy); + if (roster_alternate_buddy) + alternate_buddy = g_list_find(buddylist, roster_alternate_buddy); + // current_buddy initialization + if (!current_buddy || (g_list_position(buddylist, current_buddy) == -1)) + current_buddy = g_list_first(buddylist); +} + +// buddy_hide_group(roster, hide) +// "hide" values: 1=hide 0=show_all -1=invert +void buddy_hide_group(gpointer rosterdata, int hide) +{ + roster *roster_usr = rosterdata; + if (hide > 0) // TRUE (hide) + roster_usr->flags |= ROSTER_FLAG_HIDE; + else if (hide < 0) // NEG (invert) + roster_usr->flags ^= ROSTER_FLAG_HIDE; + else // FALSE (don't hide) + roster_usr->flags &= ~ROSTER_FLAG_HIDE; +} + +const char *buddy_getjid(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + if (!rosterdata) + return NULL; + return roster_usr->jid; +} + +// buddy_setgroup() +// Change the group of current buddy +// +// Note: buddy_setgroup() updates the user lists. +// +void buddy_setgroup(gpointer rosterdata, char *newgroupname) +{ + roster *roster_usr = rosterdata; + GSList **sl_group; + GSList *sl_newgroup; + roster *my_newgroup; + + // A group has no group :) + if (roster_usr->type & ROSTER_TYPE_GROUP) return; + + // Add newgroup if necessary + if (!newgroupname) newgroupname = ""; + sl_newgroup = roster_add_group(newgroupname); + if (!sl_newgroup) return; + my_newgroup = (roster*)sl_newgroup->data; + + // Remove the buddy from current group + sl_group = &((roster*)((GSList*)roster_usr->list)->data)->list; + *sl_group = g_slist_remove(*sl_group, rosterdata); + + // Remove old group if it is empty + if (!*sl_group) { + roster *roster_grp = (roster*)((GSList*)roster_usr->list)->data; + g_free((gchar*)roster_grp->jid); + g_free((gchar*)roster_grp->name); + g_free(roster_grp); + groups = g_slist_remove(groups, roster_grp); + } + + // Add the buddy to its new group + roster_usr->list = sl_newgroup; // (my_newgroup SList element) + my_newgroup->list = g_slist_insert_sorted(my_newgroup->list, roster_usr, + (GCompareFunc)&roster_compare_name); + + buddylist_build(); +} + +void buddy_setname(gpointer rosterdata, char *newname) +{ + roster *roster_usr = rosterdata; + GSList **sl_group; + + // TODO For groups, we need to check for unicity + // However, renaming a group boils down to moving all its buddies to + // another group, so calling this function is not really necessary... + if (roster_usr->type & ROSTER_TYPE_GROUP) return; + + if (roster_usr->name) { + g_free((gchar*)roster_usr->name); + roster_usr->name = NULL; + } + if (newname) + roster_usr->name = g_strdup(newname); + + // We need to resort the group list + sl_group = &((roster*)((GSList*)roster_usr->list)->data)->list; + *sl_group = g_slist_sort(*sl_group, (GCompareFunc)&roster_compare_name); + + buddylist_build(); +} + +const char *buddy_getname(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->name; +} + +// buddy_setnickname(buddy, newnickname) +// Only for chatrooms +void buddy_setnickname(gpointer rosterdata, const char *newname) +{ + roster *roster_usr = rosterdata; + + if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; // XXX Error message? + + if (roster_usr->nickname) { + g_free((gchar*)roster_usr->nickname); + roster_usr->nickname = NULL; + } + if (newname) + roster_usr->nickname = g_strdup(newname); +} + +const char *buddy_getnickname(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->nickname; +} + +// buddy_setinsideroom(buddy, inside) +// Only for chatrooms +void buddy_setinsideroom(gpointer rosterdata, guint inside) +{ + roster *roster_usr = rosterdata; + + if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; + + roster_usr->inside_room = inside; +} + +guint buddy_getinsideroom(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->inside_room; +} + +// buddy_settopic(buddy, newtopic) +// Only for chatrooms +void buddy_settopic(gpointer rosterdata, const char *newtopic) +{ + roster *roster_usr = rosterdata; + + if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; + + if (roster_usr->topic) { + g_free((gchar*)roster_usr->topic); + roster_usr->topic = NULL; + } + if (newtopic) + roster_usr->topic = g_strdup(newtopic); +} + +const char *buddy_gettopic(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->topic; +} + +void buddy_setprintstatus(gpointer rosterdata, enum room_printstatus pstatus) +{ + roster *roster_usr = rosterdata; + roster_usr->print_status = pstatus; +} + +enum room_printstatus buddy_getprintstatus(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->print_status; +} + +void buddy_setautowhois(gpointer rosterdata, enum room_autowhois awhois) +{ + roster *roster_usr = rosterdata; + roster_usr->auto_whois = awhois; +} + +enum room_autowhois buddy_getautowhois(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->auto_whois; +} + +// buddy_getgroupname() +// Returns a pointer on buddy's group name. +const char *buddy_getgroupname(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + + if (roster_usr->type & ROSTER_TYPE_GROUP) + return roster_usr->name; + + if (roster_usr->type & ROSTER_TYPE_SPECIAL) + return NULL; + + // This is a user + return ((roster*)((GSList*)roster_usr->list)->data)->name; +} + +// buddy_getgroup() +// Returns a pointer on buddy's group. +gpointer buddy_getgroup(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + + if (roster_usr->type & ROSTER_TYPE_GROUP) + return rosterdata; + + if (roster_usr->type & ROSTER_TYPE_SPECIAL) + return NULL; + + // This is a user + return (gpointer)((GSList*)roster_usr->list)->data; +} + +void buddy_settype(gpointer rosterdata, guint type) +{ + roster *roster_usr = rosterdata; + roster_usr->type = type; +} + +guint buddy_gettype(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->type; +} + +guint buddy_getsubscription(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->subscription; +} + +enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->status; + return offline; +} + +const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->status_msg; + return roster_usr->offline_status_message; +} + +time_t buddy_getstatustime(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->status_timestamp; + return 0; +} + +gchar buddy_getresourceprio(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->prio; + return 0; +} + +guint buddy_resource_getevents(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->events; + return ROSTER_EVENT_NONE; +} + +void buddy_resource_setevents(gpointer rosterdata, const char *resname, + guint events) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + p_res->events = events; +} + +char *buddy_resource_getcaps(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->caps; + return NULL; +} + +void buddy_resource_setcaps(gpointer rosterdata, const char *resname, + const char *caps) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) { + g_free(p_res->caps); + p_res->caps = g_strdup(caps); + } +} + +struct jep0022 *buddy_resource_jep22(gpointer rosterdata, const char *resname) +{ +#ifdef JEP0022 + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return &p_res->jep22; +#endif + return NULL; +} + +struct jep0085 *buddy_resource_jep85(gpointer rosterdata, const char *resname) +{ +#ifdef JEP0085 + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return &p_res->jep85; +#endif + return NULL; +} + +struct pgp_data *buddy_resource_pgp(gpointer rosterdata, const char *resname) +{ +#ifdef HAVE_GPGME + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return &p_res->pgpdata; +#endif + return NULL; +} + +enum imrole buddy_getrole(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->role; + return role_none; +} + +enum imaffiliation buddy_getaffil(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->affil; + return affil_none; +} + +const char *buddy_getrjid(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->realjid; + return NULL; +} + +// buddy_getresources(roster_data) +// Return a singly-linked-list of resource names +// Note: the caller should free the list (and data) after use +// If roster_data is null, the current buddy is selected +GSList *buddy_getresources(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + GSList *reslist = NULL, *lp; + + if (!roster_usr) { + if (!current_buddy) return NULL; + roster_usr = BUDDATA(current_buddy); + } + for (lp = roster_usr->resource; lp; lp = g_slist_next(lp)) + reslist = g_slist_append(reslist, g_strdup(((res*)lp->data)->name)); + + return reslist; +} + +// buddy_getresources_locale(roster_data) +// Same as buddy_getresources() but names are converted to user's locale +// Note: the caller should free the list (and data) after use +GSList *buddy_getresources_locale(gpointer rosterdata) +{ + GSList *reslist, *lp; + + reslist = buddy_getresources(rosterdata); + // Convert each item to UI's locale + for (lp = reslist; lp; lp = g_slist_next(lp)) { + gchar *oldname = lp->data; + lp->data = from_utf8(oldname); + if (lp->data) + g_free(oldname); + else + lp->data = oldname; + } + return reslist; +} + +/* +// buddy_isresource(roster_data) +// Return true if there is at least one resource +// (which means, for a room, that it isn't empty) +int buddy_isresource(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + if (!roster_usr) + return FALSE; + if (roster_usr->resource) + return TRUE; + return FALSE; +} +*/ + +// buddy_resource_setname(roster_data, oldname, newname) +// Useful for nickname change in a MUC room +void buddy_resource_setname(gpointer rosterdata, const char *resname, + const char *newname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) { + if (p_res->name) { + g_free((gchar*)p_res->name); + p_res->name = NULL; + } + if (newname) + p_res->name = g_strdup(newname); + } +} + +// buddy_del_all_resources() +// Remove all resources from the specified buddy +void buddy_del_all_resources(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + + while (roster_usr->resource) { + res *r = roster_usr->resource->data; + del_resource(roster_usr, r->name); + } +} + +// buddy_setflags() +// Set one or several flags to value (TRUE/FALSE) +void buddy_setflags(gpointer rosterdata, guint flags, guint value) +{ + roster *roster_usr = rosterdata; + if (value) + roster_usr->flags |= flags; + else + roster_usr->flags &= ~flags; +} + +guint buddy_getflags(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->flags; +} + +// buddy_setonserverflag() +// Set the on_server flag +void buddy_setonserverflag(gpointer rosterdata, guint onserver) +{ + roster *roster_usr = rosterdata; + roster_usr->on_server = onserver; +} + +guint buddy_getonserverflag(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->on_server; +} + +// buddy_search_jid(jid) +// Look for a buddy with specified jid. +// Search begins at buddylist; if no match is found in the the buddylist, +// return NULL; +GList *buddy_search_jid(const char *jid) +{ + GList *buddy; + roster *roster_usr; + + if (!buddylist) return NULL; + + for (buddy = buddylist; buddy; buddy = g_list_next(buddy)) { + roster_usr = (roster*)buddy->data; + if (roster_usr->jid && !strcasecmp(roster_usr->jid, jid)) + return buddy; + } + return NULL; +} + +// buddy_search(string) +// Look for a buddy whose name or jid contains string. +// Search begins at current_buddy; if no match is found in the the buddylist, +// return NULL; +GList *buddy_search(char *string) +{ + GList *buddy = current_buddy; + roster *roster_usr; + if (!buddylist || !current_buddy) return NULL; + for (;;) { + gchar *jid_locale, *name_locale; + char *found = NULL; + + buddy = g_list_next(buddy); + if (!buddy) + buddy = buddylist; + + roster_usr = (roster*)buddy->data; + + jid_locale = from_utf8(roster_usr->jid); + if (jid_locale) { + found = strcasestr(jid_locale, string); + g_free(jid_locale); + if (found) + return buddy; + } + name_locale = from_utf8(roster_usr->name); + if (name_locale) { + found = strcasestr(name_locale, string); + g_free(name_locale); + if (found) + return buddy; + } + + if (buddy == current_buddy) + return NULL; // Back to the beginning, and no match found + } +} + +// foreach_buddy(roster_type, pfunction, param) +// Call pfunction(buddy, param) for each buddy from the roster with +// type matching roster_type. +void foreach_buddy(guint roster_type, + void (*pfunc)(gpointer rosterdata, void *param), + void *param) +{ + GSList *sl_roster_elt = groups; + roster *roster_elt; + GSList *sl_roster_usrelt; + roster *roster_usrelt; + + while (sl_roster_elt) { // group list loop + roster_elt = (roster*) sl_roster_elt->data; + if (roster_elt->type & ROSTER_TYPE_SPECIAL) + continue; // Skip special items + sl_roster_usrelt = roster_elt->list; + while (sl_roster_usrelt) { // user list loop + roster_usrelt = (roster*) sl_roster_usrelt->data; + + if (roster_usrelt->type & roster_type) + pfunc(roster_usrelt, param); + + sl_roster_usrelt = g_slist_next(sl_roster_usrelt); + } + sl_roster_elt = g_slist_next(sl_roster_elt); + } +} + +// foreach_group_member(group, pfunction, param) +// Call pfunction(buddy, param) for each buddy in the specified group. +void foreach_group_member(gpointer groupdata, + void (*pfunc)(gpointer rosterdata, void *param), + void *param) +{ + roster *roster_elt; + GSList *sl_roster_usrelt; + roster *roster_usrelt; + + roster_elt = groupdata; + + if (!(roster_elt->type & ROSTER_TYPE_GROUP)) + return; + + sl_roster_usrelt = roster_elt->list; + while (sl_roster_usrelt) { // user list loop + roster_usrelt = (roster*) sl_roster_usrelt->data; + + pfunc(roster_usrelt, param); + sl_roster_usrelt = g_slist_next(sl_roster_usrelt); + } +} + +// compl_list(type) +// Returns a list of jid's or groups. (For commands completion) +// type: ROSTER_TYPE_USER (jid's) or ROSTER_TYPE_GROUP (group names) +// The list should be freed by the caller after use. +GSList *compl_list(guint type) +{ + GSList *list = NULL; + GSList *sl_roster_elt = groups; + roster *roster_elt; + GSList *sl_roster_usrelt; + roster *roster_usrelt; + + while (sl_roster_elt) { // group list loop + roster_elt = (roster*) sl_roster_elt->data; + + if (roster_elt->type & ROSTER_TYPE_SPECIAL) + continue; // Skip special items + + if (type == ROSTER_TYPE_GROUP) { // (group names) + if (roster_elt->name && *(roster_elt->name)) + list = g_slist_append(list, from_utf8(roster_elt->name)); + } else { // ROSTER_TYPE_USER (jid) (or agent, or chatroom...) + sl_roster_usrelt = roster_elt->list; + while (sl_roster_usrelt) { // user list loop + roster_usrelt = (roster*) sl_roster_usrelt->data; + + if (roster_usrelt->jid) + list = g_slist_append(list, from_utf8(roster_usrelt->jid)); + + sl_roster_usrelt = g_slist_next(sl_roster_usrelt); + } + } + sl_roster_elt = g_slist_next(sl_roster_elt); + } + + return list; +} + +// unread_msg(rosterdata) +// Return the next buddy with an unread message. If the parameter is NULL, +// return the first buddy with an unread message. +gpointer unread_msg(gpointer rosterdata) +{ + GSList *unread, *next_unread; + + if (!unread_list) + return NULL; + + // First unread message + if (!rosterdata) + return unread_list->data; + + unread = g_slist_find(unread_list, rosterdata); + if (!unread) + return unread_list->data; + + next_unread = g_slist_next(unread); + if (next_unread) + return next_unread->data; + return unread_list->data; +} + + +/* ### "unread_jids" functions ### + * + * The unread_jids hash table is used to keep track of the buddies with + * unread messages when a disconnection occurs. + * When removing a buddy with an unread message from the roster, the + * jid should be added to the unread_jids table. When adding a buddy to + * the roster, we check if (s)he had a pending unread message. + */ + +// unread_jid_add(jid) +// Add jid to the unread_jids hash table +void unread_jid_add(const char *jid) +{ + if (!unread_jids) { + // Initialize unread_jids hash table + unread_jids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + } + // The 2nd unread_jids is an arbitrary non-null pointer: + g_hash_table_insert(unread_jids, g_strdup(jid), unread_jids); +} + +// unread_jid_del(jid) +// Return TRUE if jid is found in the table (and remove it), FALSE if not +static int unread_jid_del(const char *jid) +{ + if (!unread_jids) + return FALSE; + return g_hash_table_remove(unread_jids, jid); +} + +// Helper function for unread_jid_get_list() +static void add_to_unreadjids(gpointer key, gpointer value, gpointer udata) +{ + GList **listp = udata; + *listp = g_list_append(*listp, key); +} + +// unread_jid_get_list() +// Return the JID list. +// The content of the list should not be modified or freed. +// The caller should call g_list_free() after use. +GList *unread_jid_get_list(void) +{ + GList *list = NULL; + + if (!unread_jids) + return NULL; + + // g_hash_table_get_keys() is only in glib >= 2.14 + //return g_hash_table_get_keys(unread_jids); + + g_hash_table_foreach(unread_jids, add_to_unreadjids, &list); + return list; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */